Когда метод должен быть объявлен синхронизированным? - PullRequest
0 голосов
/ 19 декабря 2018

Ниже приведена простая, работающая программа, которая использует два потока для печати счетчика:

public class SynchronizedCounter implements Runnable {

    private static int i = 0;

    public void increment() { i++; }
    public int getValue() { return i; }  

    @Override
    public void run() {
        for( int i = 0; i < 5; i++ ) {
            synchronized( this ) {
                increment();
                System.out.println( Thread.currentThread().getName() + " counter: " + this.getValue() );
            }
        }
    }

    public static void main( String[] args ) {

        ExecutorService executorService = Executors.newFixedThreadPool( 2 );
        SynchronizedCounter synchronizedCounter = new SynchronizedCounter();
        executorService.submit( synchronizedCounter );
        executorService.submit( synchronizedCounter );  
        executorService.shutdown();

    }

}

Вывод соответствует ожидаемому - счетчик двух потоков отображается в порядке.

Еслиincrement() и getValue() объявляются как synchronized, а код блока synchronized комментируется, выходные данные проявляют проблемы с видимостью и расой.

Поскольку increment() и getValue() не объявлены как synchronized и, просто используя блок synchronized (передавая объект монитора), если синхронизация может быть достигнута, при каких обстоятельствах они должны быть объявлены как synchronized?

Ответы [ 3 ]

0 голосов
/ 19 декабря 2018

В вашем коде сам класс не является потокобезопасным, и от клиента (вашего метода run) зависит обеспечение безопасности потока.

Если вы хотите сделать поток класса безопасным, чтобыклиентам не нужно делать что-то конкретное, чтобы использовать это, оно должно иметь надлежащие механизмы синхронизации и предлагать метод «более высокого уровня», который может запускать атомарные операции при комбинации операций, если необходимо.

Подумайте о AtomicIntegerНапример, у которого есть метод incrementAndGet(), который обеспечит превосходный подход.Ваш run метод станет:

for( int i = 0; i < 5; i++ ) {
  int newValue = counter.incrementAndGet();
  System.out.println(Thread.currentThread().getName() + " counter: " + newValue );
}
0 голосов
/ 19 декабря 2018

Ваш increment() метод использует i++, который фактически заменяется 3 инструкциями.Если этот метод не объявлен как synchronized, то несколько потоков могут выполнить его одновременно, что приведет к условиям состязания и неверным результатам.

Если increment () и getValue () объявлены какСинхронизированный и синхронизированный код блока комментируется, на выходе появляются проблемы видимости и гонки.

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

Поэтому вам нужно синхронизировать increment() и getValue(), если вы планируете вызывать их вне блока synchronized в вашем методе run (и поскольку они объявлены как public, существуетвозможность совершать такие звонки).

0 голосов
/ 19 декабря 2018

Объявите метод синхронизированным, когда вам нужна семантика синхронизированного блока вокруг тела метода.

synchronized void increment() {
  ...
}

точно так же, как:

void increment() {
  synchronized (this) { ... }
}

в приведенном выше коде вы больше не выполняете методы increment() и getValue() атомарно: другой поток может подключиться и выполнить методы между вызовами вашего потока:

Thread 1             Thread 2

increment()
                     increment()
                     getValue()
getValue()

Thisвозможно, но два потока не могут выполнить increment() (или getValue()) одновременно, потому что они синхронизированы (*).

С другой стороны, синхронизация вокруг вызовов, как вКод в вопросе означает, что два вызова выполняются атомарно, поэтому два потока не могут чередоваться:

Thread 1             Thread 2

increment()
getValue()
                     increment()
                     getValue()

(*) На самом деле, они оба могут выполнять метод одновременно,Просто все потоки, кроме одного, будут ждать на synchronized (this).

...