Synchronized блок в java

Synchronized Methods

The Java programming language provides two basic synchronization idioms: synchronized methods and synchronized statements. The more complex of the two, synchronized statements, are described in the next section. This section is about synchronized methods.

To make a method synchronized, simply add the synchronized keyword to its declaration:

public class SynchronizedCounter < private int c = 0; public synchronized void increment() < c++; >public synchronized void decrement() < c--; >public synchronized int value() < return c; >>

If count is an instance of SynchronizedCounter , then making these methods synchronized has two effects:

  • First, it is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.
  • Second, when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocation of a synchronized method for the same object. This guarantees that changes to the state of the object are visible to all threads.

Note that constructors cannot be synchronized — using the synchronized keyword with a constructor is a syntax error. Synchronizing constructors doesn’t make sense, because only the thread that creates an object should have access to it while it is being constructed.

Warning: When constructing an object that will be shared between threads, be very careful that a reference to the object does not «leak» prematurely. For example, suppose you want to maintain a List called instances containing every instance of class. You might be tempted to add the following line to your constructor:

Читайте также:  Search form html and css

But then other threads can use instances to access the object before construction of the object is complete.

Synchronized methods enable a simple strategy for preventing thread interference and memory consistency errors: if an object is visible to more than one thread, all reads or writes to that object’s variables are done through synchronized methods. (An important exception: final fields, which cannot be modified after the object is constructed, can be safely read through non-synchronized methods, once the object is constructed) This strategy is effective, but can present problems with liveness, as we’ll see later in this lesson.

Источник

Руководство по синхронизированному ключевому слову в Java

Этот краткий учебник будет введением в использование блока synchronized в Java.

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

Часть логики, помеченная как синхронизированная , становится синхронизированным блоком, позволяя выполняться только одному потоку в любой момент времени .

2. Почему синхронизация?​

Давайте рассмотрим типичное состояние гонки, когда мы вычисляем сумму, а несколько потоков выполняют метод calculate() :

 public class ForEachSynchronizedMethods     private int sum = 0;    public void calculate()    setSum(getSum() + 1);   >    // standard setters and getters   > 

Тогда давайте напишем простой тест:

 @Test   public void givenMultiThread_whenNonSyncMethod()    ExecutorService service = Executors.newFixedThreadPool(3);   ForEachSynchronizedMethods summation = new ForEachSynchronizedMethods();    IntStream.range(0, 1000)   .forEach(count -> service.submit(summation::calculate));   service.awaitTermination(1000, TimeUnit.MILLISECONDS);    assertEquals(1000, summation.getSum());   > 

Мы используем ExecutorService с пулом из 3 потоков для выполнения calculate() 1000 раз.

Если бы мы выполняли это последовательно, ожидаемый результат был бы 1000, но наше многопоточное выполнение почти каждый раз терпит неудачу с несогласованным фактическим результатом:

 java.lang.AssertionError: expected:1000> but was:965>  at org.junit.Assert.fail(Assert.java:88)  at org.junit.Assert.failNotEquals(Assert.java:834)   ... 

Конечно, мы не находим этот результат неожиданным.

Простой способ избежать состояния гонки — сделать операцию потокобезопасной с помощью ключевого слова synchronized .

3. Синхронизированное ключевое слово

Мы можем использовать синхронизированное ключевое слово на разных уровнях:

Когда мы используем синхронизированный блок, Java внутренне использует монитор , также известный как блокировка монитора или встроенная блокировка, для обеспечения синхронизации. Эти мониторы привязаны к объекту; следовательно, все синхронизированные блоки одного и того же объекта могут иметь только один поток, выполняющий их одновременно.

3.1. Синхронизированные методы экземпляра

Мы можем добавить ключевое слово synchronized в объявление метода, чтобы сделать метод синхронизированным:

 public synchronized void synchronisedCalculate()    setSum(getSum() + 1);   > 

Обратите внимание, что как только мы синхронизируем метод, тестовый пример проходит с фактическим выходом как 1000:

 @Test   public void givenMultiThread_whenMethodSync()    ExecutorService service = Executors.newFixedThreadPool(3);   SynchronizedMethods method = new SynchronizedMethods();    IntStream.range(0, 1000)   .forEach(count -> service.submit(method::synchronisedCalculate));   service.awaitTermination(1000, TimeUnit.MILLISECONDS);    assertEquals(1000, method.getSum());   > 

Методы экземпляра синхронизируются с экземпляром класса, владеющего методом, что означает, что только один поток для каждого экземпляра класса может выполнять этот метод.

3.2. Синхронизированные статические методы _

Статические методы синхронизируются так же, как и методы экземпляра:

 public static synchronized void syncStaticCalculate()    staticSum = staticSum + 1;   > 

Эти методы синхронизируются с объектом класса , связанным с классом. Поскольку для каждой JVM существует только один объект Class , только один поток может выполняться внутри статического синхронизированного метода для каждого класса, независимо от количества экземпляров, которые он имеет.

 @Test   public void givenMultiThread_whenStaticSyncMethod()    ExecutorService service = Executors.newCachedThreadPool();    IntStream.range(0, 1000)   .forEach(count ->   service.submit(ForEachSynchronizedMethods::syncStaticCalculate));   service.awaitTermination(100, TimeUnit.MILLISECONDS);    assertEquals(1000, ForEachSynchronizedMethods.staticSum);   > 

3.3. Синхронизированные блоки внутри методов

Иногда мы не хотим синхронизировать весь метод, а только некоторые инструкции внутри него. Мы можем добиться этого, применив синхронизацию к блоку:

 public void performSynchronisedTask()    synchronized (this)    setCount(getCount()+1);   >   > 

Затем мы можем проверить изменение:

 @Test   public void givenMultiThread_whenBlockSync()    ExecutorService service = Executors.newFixedThreadPool(3);   ForEachSynchronizedBlocks synchronizedBlocks = new ForEachSynchronizedBlocks();    IntStream.range(0, 1000)   .forEach(count ->   service.submit(synchronizedBlocks::performSynchronisedTask));   service.awaitTermination(100, TimeUnit.MILLISECONDS);    assertEquals(1000, synchronizedBlocks.getCount());   > 

Обратите внимание, что мы передали параметр this в синхронизированный блок. Это объект монитора. Код внутри блока синхронизируется с объектом монитора. Проще говоря, внутри этого блока кода может выполняться только один поток для каждого объекта монитора.

Если бы метод был статическим , мы бы передали имя класса вместо ссылки на объект, и класс был бы монитором для синхронизации блока:

 public static void performStaticSyncTask()  synchronized (SynchronisedBlocks.class)   setStaticCount(getStaticCount() + 1);  > > 

Протестируем блок внутри статического метода:

 @Test   public void givenMultiThread_whenStaticSyncBlock()    ExecutorService service = Executors.newCachedThreadPool();    IntStream.range(0, 1000)   .forEach(count ->   service.submit(ForEachSynchronizedBlocks::performStaticSyncTask));   service.awaitTermination(100, TimeUnit.MILLISECONDS);    assertEquals(1000, ForEachSynchronizedBlocks.getStaticCount());   > 

3.4. Повторный вход​

Блокировка синхронизированных методов и блоков является повторно используемой. Это означает, что текущий поток может получать одну и ту же синхронизированную блокировку снова и снова, удерживая ее:

 Object lock = new Object();   synchronized (lock)    System.out.println("First time acquiring it");    synchronized (lock)    System.out.println("Entering again");    synchronized (lock)    System.out.println("And again");   >   >   > 

Как показано выше, пока мы находимся в синхронизированном блоке, мы можем многократно получать одну и ту же блокировку монитора.

4. Вывод

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

Мы также узнали, как состояние гонки может повлиять на наше приложение и как синхронизация помогает нам этого избежать. Подробнее о безопасности потоков при использовании блокировок в Java см. в нашей статье java.util.concurrent.Locks .

Полный код для этой статьи доступен на GitHub .

Источник

Оцените статью