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

1. Общ преглед

Тази кратка статия ще бъде въведение в използването на синхронизирания блок в Java.

Най-просто казано, в среда с много нишки, състояние на състезанието възниква, когато две или повече нишки се опитват да актуализират изменяеми споделени данни едновременно. Java предлага механизъм за избягване на състезателни условия чрез синхронизиране на достъпа на нишки до споделени данни.

Част от логиката, маркирана със синхронизирано, се превръща в синхронизиран блок, позволяващ да се изпълнява само една нишка във всеки един момент .

2. Защо синхронизация?

Нека разгледаме типично състезателно условие, при което изчисляваме сумата и множество нишки изпълняват метода Calculate () :

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

И нека напишем прост тест:

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

Ние просто използваме ExecutorService с пул с 3 нишки, за да изпълним изчислението () 1000 пъти.

Ако бихме изпълнили това последователно, очакваната продукция ще бъде 1000, но нашето многонишко изпълнение ще се провали почти всеки път с несъответстващ действителен изход, напр .:

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

Този резултат, разбира се, не е неочакван.

Един лесен начин да се избегне състезателното състояние е да се направи операцията безопасна за нишки, като се използва синхронизираната ключова дума.

3. Синхронизираната ключова дума

В синхрон ключова дума може да се използва на различни нива:

  • Инстанционни методи
  • Статични методи
  • Кодови блокове

Когато използваме синхронизиран блок, Java използва вътрешно монитор, известен също като заключване на монитора или вътрешно заключване, за да осигури синхронизация. Тези монитори са обвързани с обект, като по този начин всички синхронизирани блокове на един и същ обект могат да имат само една нишка, която ги изпълнява едновременно.

3.1. Синхронизирани методи на инстанция

Просто добавете синхронизираната ключова дума в декларацията на метода, за да направите метода синхронизиран:

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 на клас, само една нишка може да се изпълни в един статичен синхронизирано метод на клас, независимо от броя на случаи има.

Нека го тестваме:

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

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

Понякога не искаме да синхронизираме целия метод, а само някои инструкции в него. Това може да се постигне чрез прилагане на синхронизирано към блок:

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

Нека тестваме промяната:

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

Забележете, че предадохме параметър този на синхронизирания блок. Това е обектът на монитора, кодът в блока се синхронизира на обекта на монитора. Просто казано, само една нишка на обект на монитор може да изпълни вътре в този блок код.

В случай, че методът е статичен , ние бихме предали името на класа вместо референцията на обекта. А класът ще бъде монитор за синхронизация на блока:

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(BaeldungSynchronizedBlocks::performStaticSyncTask)); service.awaitTermination(100, TimeUnit.MILLISECONDS); assertEquals(1000, BaeldungSynchronizedBlocks.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. Заключение

В тази бърза статия видяхме различни начини за използване на синхронизираната ключова дума за постигане на синхронизация на нишки.

Също така проучихме как дадено състезателно състояние може да повлияе на нашето приложение и как синхронизацията ни помага да избегнем това. За повече информация относно безопасността на нишките при използване на ключалки в Java вижте нашата статия java.util.concurrent.Locks .

Пълният код за този урок е достъпен в GitHub.