2016-09-28 4 views
5

Вот мой код:Почему синхронизация не работает должным образом?

private int count = 0; 

    public synchronized void increment() { 
     count++; 
    } 

public void doWork() throws InterruptedException { 

    Thread t1 = new Thread(new Runnable() { 
     public void run() { 
      for (int i = 0; i < 5; i++) { 
       increment(); 
       System.out.println(count+" "+Thread.currentThread().getName()); 
      }}}); 

    Thread t2 = new Thread(new Runnable() { 
     public void run() { 
      for (int i = 0; i < 5; i++) { 
       increment(); 
       System.out.println(count+" "+Thread.currentThread().getName()); 
      }}}); 

    t1.start(); 
    t2.start(); 
} 

Вот мой результат:

2 Thread-1 
2 Thread-0 
3 Thread-1 
5 Thread-1 
6 Thread-1 
4 Thread-0 
8 Thread-0 
9 Thread-0 
7 Thread-1 
10 Thread-0 

Я понимаю, что increment является synchronized. Итак, сначала должно быть increment, а затем отпустите lock, а затем введите lock в резьбу t1 или t2. Итак, должно быть increment по одному числу за раз, правильно?

Но почему мой код incrementing два или три числа за раз? Я что-то делаю неправильно (я новичок)?

+9

'increment'' 'synchronized', но' count' нет. Два выражения 'increment()' и 'System.out.println()' не являются атомарными. – bradimus

ответ

5

В то время как count++; действительно синхронизирован System.out.println(count+" "+Thread.currentThread().getName()); нет, но он имеет доступ к переменной count.

Даже если вы синхронизации доступа, это не поможет, потому что следующий сценарий будет еще возможно:

  • Thread 1 приращение
  • резьбы 2 инкремент
  • Тема 1 Значение печати 2
  • Резьба 2 значение печати 2

Для решения этой проблемы вам необходимо увеличивать и печатать в том же синхронизированном разделе. Например, вы можете поставить System.out.println(count+" "+Thread.currentThread().getName()); в метод increment.

+1

Или сделайте 'count' изменчивым. – chrylis

+0

Спасибо, это сработало. ура! –

+0

@chrylis Он разрешит первую проблему, но не вторую. – talex

0

Что на самом деле происходит, так это то, что ваши потоки являются моментальными снимками (возможно, здесь лучше другое слово) текущее значение значение переменной count и отображение его. Вы можете думать об этом, как будто есть синее ведро с нулевым числом, и оба Threads получают одинаковое ведро в том же цвете и количестве. Теперь они работают индивидуально на этих ведрах.

Если вы хотите, чтобы они работали на одном ковше, вы должны сделать их атомарными, например. с AtomicInteger или volatile или любым другим инструментом из параллельного пакета java.

1

increment метод может быть запущен на другом потоке после increment метод возвращает, но перед count извлекается для конкатенации

count+" "+Thread.currentThread().getName() 

Вы могли бы, например, исправить это путем изменения и извлечения count в одном синхронизированного блока:

public synchronized int incrementAndGet() { 
    count++; 
    return count; // read access synchronized 
} 
for (int i = 0; i < 5; i++) { 
    System.out.println(incrementAndGet()+" "+Thread.currentThread().getName()); 
} 

Или использовать the class in the standard library specifically designed for this purpose:

private final AtomicInteger counter = new AtomicInteger(0); 

public void doWork() throws InterruptedException { 

    Thread t1 = new Thread(new Runnable() { 
     public void run() { 
      for (int i = 0; i < 5; i++) { 
       System.out.println(counter.incrementAndGet() + " " + Thread.currentThread().getName()); 
      } 
     } 
    }); 

    Thread t2 = new Thread(new Runnable() { 
     public void run() { 
      for (int i = 0; i < 5; i++) { 
       System.out.println(counter.incrementAndGet() + " " + Thread.currentThread().getName()); 
      } 
     } 
    }); 

    t1.start(); 
    t2.start(); 
} 

Конечно, это не обязательно приведет чисел от 1 до 10, которое печатается в просто так, что число не получено более одного раза.Следующий вывод может произойти:

2 Thread-0 
3 Thread-0 
4 Thread-0 
1 Thread-1 
5 Thread-0 
6 Thread-1 
7 Thread-0 
8 Thread-1 
9 Thread-1 
10 Thread-1 
+0

Я пробовал это, но он не работает. –

+0

@Hemlata О, это действительно работает, но вы не можете ожидать, что операторы печати будут синхронизированы, не делая этого явно, и вы не упомянули о таком ограничении в вопросе. (Число не встречается более одного раза, номера не хватает, цифры печатаются в порядке возрастания в каждом потоке.) Кроме того, использование нескольких потоков в этом сценарии не имеет смысла ... – fabian

+0

Возможно, я делаю что-то неправильно, поэтому он не работает, поскольку я новичок. –

0

Решения 1: Предоставлено Фабианом. Чтобы дать одну функцию incrementAndGet().

Раствор 2:synchronized блок вместо synchronized метод (если это возможно):

Полный код будет, как:

private int count = 0; 
private Object dummyObject = new Object(); 

public void increment() { 
    count++; 
} 

public int getCount() { 
    return count; 
} 

public void doWork() throws InterruptedException { 

    Thread t1 = new Thread(new Runnable() { 
     public void run() { 
      for (int i = 0; i < 5; i++) { 
       synchronized (dummyObject) { 
        increment(); 
        System.out.println(count + " " + Thread.currentThread().getName()); 
       } 
      } 
     } 
    }); 

    Thread t2 = new Thread(new Runnable() { 
     public void run() { 
      for (int i = 0; i < 5; i++) { 
       synchronized (dummyObject) { 
        increment(); 
        System.out.println(count + " " + Thread.currentThread().getName()); 
       } 
      } 
     } 
    }); 

    t1.start(); 
    t2.start(); 
} 
+0

Это не помогает, поскольку значение по-прежнему может быть изменено между вызовом 'increment' и вызовом' getCount', так как в этом случае монитор приобретается дважды. – fabian

0

Один из альтернативных вариантов решения без использования synchronized.

Поскольку ваш случай использования простой (только incrimenting счетчик и вывести значение, AtomicInteger является лучшим выбором

import java.util.concurrent.atomic.AtomicInteger; 

public class TestCounter{ 
    private AtomicInteger count = new AtomicInteger(0); 

    public void doWork() throws InterruptedException { 

     Thread t1 = new Thread(new Runnable() { 
      public void run() { 
       for (int i = 0; i < 5; i++) { 
        System.out.println(""+Thread.currentThread().getName()+":"+count.incrementAndGet()); 
       }}}); 

     Thread t2 = new Thread(new Runnable() { 
      public void run() { 
       for (int i = 0; i < 5; i++) { 
        System.out.println(""+Thread.currentThread().getName()+":"+count.incrementAndGet()); 
       }}}); 

     t1.start(); 
     t2.start(); 
    } 

    public static void main(String args[]) throws Exception{ 
     TestCounter tc = new TestCounter(); 
     tc.doWork(); 
    } 
} 

выход:.

Thread-0:1 
Thread-0:3 
Thread-0:4 
Thread-0:5 
Thread-0:6 
Thread-1:2 
Thread-1:7 
Thread-1:8 
Thread-1:9 
Thread-1:10 

См @fabian ответ, почему эти цифры и не печатаются последовательно.

Если вы ожидаете последовательную последовательность чисел в порядке возрастания от 1 до 10, нити не требуются.