Зачем считать меньше даже использованного синхронизированного ключевого слова? - PullRequest
3 голосов
/ 27 февраля 2012

Я начинаю изучать некоторые концепции Java-параллелизма и использования. Но один из этих фрагментов кода мне не понятен.

public class Count { 
    private int count = 0; 
    public synchronized void setCount(int count) { 
        this.count = count; 
    } 
    public synchronized int getCount() { 
        return count; 
    } 
} 
class CountRunner extends Thread { 
    Count count; 
    public CountRunner(Count count) { 
        this.count = count; 
    } 
    public void run() { 
        for (int i = 1; i <= 1000; i++) { 
            count.setCount(count.getCount() + 1); 
        } 
    } 
} 
class TestCount { 
    public static void main(String[] args) throws Exception { 
        Count count = new Count(); 
        CountRunner runnerA = new CountRunner(count); 
        CountRunner runnerB = new CountRunner(count); 
        runnerA.start(); 
        runnerB.start();         
        runnerA.join(); //join statement here 
        runnerB.join(); 
        System.out.println("count.getCount = " + count.getCount()); 
    } 
} 
Question:
1. The result is a little less than 2000 for many times, why ?
2. if delete 2 join() statement, why count.getCount = 451,even less ?
3. i think there will be no effect of deleting the join() statements,
because i already have Synchronized method to lock one object to one thread each time ? 
So, What's the point of using Synchronized and join() ?

Ответы [ 5 ]

4 голосов
/ 27 февраля 2012
  1. Если вы прервете линию

    count.setCount (count.getCount () + 1);

в 3 отдельных строки, будет понятнее:

final int oldCount = count.getCount(); // a
final int newCount = oldCount + 1; // b
count.setCount(newCount); // c

Обратите внимание, что хотя операторы (a) и (c) синхронизированы, весь блок равен , а не . Таким образом, они все еще могут чередоваться, что означает, что поток A может вводить / выполнять оператор (a) после того, как поток B выполняет поток (a), но за до он завершает / вводит оператор (c). Когда это происходит, потоки (a) и (b) будут иметь одинаковые oldCount и, следовательно, пропускают одно приращение.

2

join () должен удостовериться, что оба потока A и Thread B заканчиваются перед печатью Причина в том, что вы получаете меньшее число, потому что, когда вы печатаете результаты, потоки A и B, возможно, еще не завершили работу. Другими словами, даже если вы синхронизируете идеально, без join (), у вас все равно будет число, намного меньшее, чем 2000.

3. См. Ответ на вопрос 2.

4 голосов
/ 27 февраля 2012

Это довольно просто.Вы вызываете метод setCount, вызывая getCount + 1. Перед входом в метод среда выполнения оценивает getCount (синхронизированный), но вы не удерживаете блокировку при выходе из getCount и при входе в setCount, и другие потоки могут ввести вызов getCount.Таким образом, время от времени два (или более, в зависимости от того, сколько потоков вы создаете) будут иметь одинаковое значение в getCount.Представьте, что поток A входит и получает значение 1 в getCount.Среда выполнения возвращает выполнение к шагу B, который вызывает getCount и получает то же значение 1. Поток B устанавливает значение в 1 и делает еще 50 запусков, поэтому на этом этапе ваш счет будет равен 50.Среда выполнения передает выполнение потоку A, который вызывает setCount с 1 (помните, что ему не удалось вызвать setCount, и он получил свой exec).Теперь A устанавливает значение 1 (что неверно).

Измените запущенную реализацию следующим образом:

public void run() { 
    for (int i = 1; i <= 1000; i++) {
      synchronized(count){ 
        count.setCount(count.getCount() + 1); 
      }
    } 
} 
3 голосов
/ 27 февраля 2012

1) Потому что вы не правильно блокируете.Вы звоните getCount(), который блокирует, получает значение и разблокирует, увеличивает и вызывает setCount(), который блокирует, сохраняет значение и разблокирует.В некоторых случаях происходит то, что оба потока вызывают getCount(), первый протектор блокируется, получает значение x и разблокируется.Затем второй поток получает блокировку, получает то же значение x и разблокирует.Поскольку оба потока увеличивают, а затем сохраняют одно и то же значение, вы получите меньшее число, чем ожидалось.

2) Без join() вы не будете ждать окончания потоков, ваш основной поток просто вызоветgetCount() и получите случайное значение, пока потоки еще работают.

3) Поскольку другие потоки не удерживают блокировку все время (если вы хотите, чтобы они оба работали,в конце концов они должны дать друг другу разблокированное время), вам понадобится еще join().

1 голос
/ 27 февраля 2012

Ответ в одну строку состоит в том, что count.setCount(count.getCount() + 1); не является атомарной операцией.

Или быть немного менее оракулярным, в то время как setCount и getCount являются должным образом поточно-ориентированными и атомарными, в этом нет ничегоостановка другого потока от вызова любого из этих методов между вызовом этого потока setCount и getCount.Это приведет к потере счета.

Один из способов избежать потери счета - создать атомарную операцию increment().

1 голос
/ 27 февраля 2012

someThread.join() заставит вызывающий Thread ждать, пока someThread не завершится.

Если вы удалите вызовы join(), то главный Thread может позвонить getCount() до того, каксчет окончен, потому что someThread может все еще выполняться.

Синхронизированный метод просто означает, что более чем один синхронизированный вызов к Object не может быть выполнен в одновременно .

...