2014-12-19 2 views
2

Вы можете скопировать вставку приведенного ниже примера для запуска на вашем локальном компьютере. Я изучаю MultiThreading, и я наткнулся на этот пример онлайн. Я смущен тем, почему метод add (внутри класса Counter) может иметь значение, если объявлено синхронизированным, вы можете удалить синхронизированное из него ключевое слово, и это не повлияет на конечный результат, поэтому мне просто интересно, какова цель, которую нужно обслуживать объявив его синхронизированнымКакова цель объявления метода, синхронизированного в следующем примере:

class Counter { 

    long count = 0; 

    public synchronized void add(long value) { 
     this.count += value; 
     System.out.println(count + "-" + Thread.currentThread().getName()); 
    } 
} 

class CounterThread extends Thread { 

    protected Counter counter = null; 

    public CounterThread(Counter counter) { 
     this.counter = counter; 
    } 

    public void run() { 
     for (int i = 0; i < 10; i++) { 
      try { 
       Thread.sleep(100); 
      } catch (InterruptedException ex) { 
       Logger.getLogger(CounterThread.class.getName()).log(Level.SEVERE, null, ex); 
      } 
      counter.add(i); 
     } 
    } 
} 

public class Example { 

    public static void main(String[] args) { 
     Counter counter = new Counter(); 
     Thread threadA = new CounterThread(counter); 
     Thread threadB = new CounterThread(counter); 
     Thread threadC = new CounterThread(counter); 

     threadA.start(); 
     threadB.start(); 
     threadC.start(); 
    } 
} 
+0

Если вы удалили спящий режим и увеличили его до 100000000 итераций или так (чтобы он не сразу завершился) и напечатайте значение счетчика в конце (после того, как основной поток присоединяется к другим потокам), вы будете см. разницу. – immibis

+0

@immibis Да, я сделал подобный эксперимент, и конечный результат не был тем, что ожидалось. –

ответ

4

Ключевое слово synchronized предотвращает выполнение операций ThreadA, threadB и threadC в одно и то же время. Важно понимать, что this.count += value внутренне реализовать в виде трех операций:

  1. счетчик чтения поле
  2. добавить значение
  3. счетчик записи поля

Если удалить synchronized ключевое слово, то вы можете себе представить следующая последовательность событий:

  1. потокA читает кол-во Поле (0)
  2. threadB считывает поле счетчика (0)
  3. ThreadA добавляет значение для подсчета (кол = 0 от # 1 + значение = 1, например)
  4. threadB добавляет значение для подсчета (кол = 0 от # 2 + значение = 1, например)
  5. ThreadA записывает счета поле (1 из # 3)
  6. threadB записывает счета поле (1 из # 4 (!), так что операция записи в # 5 теряется)
+0

А я вижу. В настоящее время удаление синхронизированного кода не влияет на код, но я думаю, что если процесс обновления значения был дольше, тогда есть вероятность, что чтение происходит из нескольких потоков одновременно. Понял. Большое спасибо bkail. –

+1

Да, это правильно, и закон Мерфи говорит, что это произойдет, когда код будет в производстве :-). –

+0

Мерфи - чертовски правая !! –

4

объявив класс как synchronized означает, что любой поток хочет выполнить метод должен сначала получить блокировку текущего объекта. Объект имеет ровно один замок, и только тот поток, который его получил, может выполнить этот метод. Другие потоки должны ждать, пока не будет выполнена фиксация фиксации. По завершении метода исполняющий поток освобождает блокировку.

Вкратце. ключевое слово synchronized гарантирует, что в любой момент времени только один поток может выполнить метод.

Синхронизация важна для классов, где (а) система многопоточная; (b) Класс имеет переменные экземпляра, которые могут быть изменены внутри метода. Проблемы многопоточности обычно возникают, когда два или более потока выполняют один и тот же метод одновременно. Один поток может изменить переменную экземпляра на основе ее входов, и эти изменения видны другим потоком.

В этом конкретном примере может возникнуть проблема с переменной экземпляра count. Если один поток устанавливает его, чтобы сказать 5, второй поток может прийти и установить его на 10. Когда первый поток выполняет вызов System.out.println, он печатает значение 10 - это неожиданное поведение.

+0

Я вижу, где он может потерпеть неудачу. Благодарю. –

3

Причина, по которой важно провести синхронизацию метода add(), заключается в том, что эта строка:

this.count += value; 

Не является атомным действием.Это действительно компилируется как это (или аналогичный):

long c = count; 
c = c + value; 
count = c; 

Что должно быть довольно очевидно, не поточно.

Смежные вопросы