Sprievodca synchronizovaným kľúčovým slovom v jazyku Java

1. Prehľad

Tento rýchly článok bude úvodom do používania synchronizované blok v Jave.

Zjednodušene možno povedať, že v prostredí s viacerými vláknami dôjde k sporu, keď sa dve alebo viac vlákien pokúsi aktualizovať premenlivé zdieľané údaje súčasne. Java ponúka mechanizmus na zabránenie rasovým podmienkam synchronizáciou prístupu vlákien k zdieľaným údajom.

Kus logiky označený synchronizované stane sa synchronizovaným blokom, čo umožňuje spustenie iba jedného vlákna v danom okamihu.

2. Prečo synchronizácia?

Uvažujme o typickej rasovej podmienke, pri ktorej vypočítame súčet a viaceré vlákna vykonajú vypočítať () metóda:

verejná trieda BaeldungSynchronizedMethods {private int sum = 0; public void spočítať () {setSum (getSum () + 1); } // štandardní zakladatelia a obstarávatelia} 

A poďme napísať jednoduchý test:

@ Test public void givenMultiThread_whenNonSyncMethod () {služba ExecutorService = Executors.newFixedThreadPool (3); Súčet BaeldungSynchronizedMethods = nový BaeldungSynchronizedMethods (); IntStream.range (0, 1000) .forEach (count -> service.submit (sumarácia :: výpočet)); service.awaitTermination (1000, TimeUnit.MILLISECONDS); assertEquals (1000, summation.getSum ()); }

Jednoducho používame ExecutorService s 3-vláknovým fondom na vykonanie vypočítať () 1000 krát.

Ak by sme to vykonávali sériovo, očakávaný výstup by bol 1000, ale naše vykonávanie viacerých vlákien zlyhá takmer vždy s nekonzistentným skutočným výstupom, napr .:

java.lang.AssertionError: očakáva sa: ale bolo: na org.junit.Assert.fail (Assert.java:88) na org.junit.Assert.failNotEquals (Assert.java:834) ...

Tento výsledok samozrejme nie je neočakávaný.

Jednoduchým spôsobom, ako sa vyhnúť podmienkam rasy, je zaistiť bezpečnosť operácie pomocou vlákna synchronizované kľúčové slovo.

3. The Synchronizované Kľúčové slovo

The synchronizované kľúčové slovo je možné použiť na rôznych úrovniach:

  • Inštančné metódy
  • Statické metódy
  • Bloky kódu

Keď použijeme a synchronizované blok, interne Java používa na zabezpečenie synchronizácie monitor tiež známy ako zámok monitora alebo vnútorný zámok. Tieto monitory sú viazané na objekt, takže všetky synchronizované bloky toho istého objektu môžu mať iba jedno vlákno, ktoré ich vykonáva súčasne.

3.1. Synchronizované Metódy inštancie

Stačí pridať synchronizované kľúčové slovo v deklarácii metódy, aby sa metóda synchronizovala:

public synchronized void synchronisedCalculate () {setSum (getSum () + 1); }

Všimnite si, že akonáhle synchronizujeme metódu, testovací prípad prejde so skutočným výstupom 1 000:

@Test public void givenMultiThread_whenMethodSync () {služba ExecutorService = Executors.newFixedThreadPool (3); Metóda SynchronizedMethods = nová SynchronizedMethods (); IntStream.range (0, 1000) .forEach (count -> service.submit (method :: synchronisedCalculate)); service.awaitTermination (1000, TimeUnit.MILLISECONDS); assertEquals (1000, method.getSum ()); }

Metódy inštancie sú synchronizované nad inštanciou triedy vlastniacej metódu. Čo znamená, že túto metódu môže vykonať iba jedno vlákno na inštanciu triedy.

3.2. Synchronizované Static Metódy

Statické metódy sú synchronizované rovnako ako inštančné metódy:

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

Tieto metódy sú synchronizované na Trieda objekt spojený s triedou a keďže iba jeden Trieda objekt existuje na JVM na triedu, vo vnútri a statický synchronizované metóda pre triedu, bez ohľadu na počet prípadov, ktoré má.

Poďme to vyskúšať:

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

3.3. Synchronizované Blokuje v rámci metód

Niekedy nechceme synchronizovať celú metódu, ale iba niektoré pokyny v nej. Toto je možné dosiahnuť uplatnenie synchronizované do bloku:

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

Otestujme zmenu:

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

Všimnite si, že sme odovzdali parameter toto do synchronizované blokovať. Toto je objekt monitora, kód vo vnútri bloku sa synchronizuje s objektom monitora. Jednoducho povedané, vnútri tohto bloku kódu sa môže vykonať iba jedno vlákno na objekt monitora.

V prípade, že metóda je statický, namiesto odkazu na objekt by sme odovzdali názov triedy. A trieda by bola monitorom synchronizácie bloku:

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

Vyskúšajme blok vo vnútri statický metóda:

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

3.4. Opakované prijímanie

Zámok za synchronizované metódy a bloky je opätovne zadané. To znamená, že súčasné vlákno môže získať to isté synchronizované držte ho znova a znova:

Uzamknutie objektu = nový objekt (); synchronized (lock) {System.out.println ("Prvé získanie"); synchronized (lock) {System.out.println ("Opäť vstupujem"); synchronized (lock) {System.out.println ("A znova"); }}}

Ako je uvedené vyššie, zatiaľ čo sme v synchronizované blok, môžeme získať ten istý zámok monitora opakovane.

4. Záver

V tomto rýchlom článku sme videli rôzne spôsoby použitia synchronizované kľúčové slovo na dosiahnutie synchronizácie vlákien.

Skúmali sme tiež to, ako môžu závodné podmienky ovplyvniť našu aplikáciu a ako nám to synchronizácia pomáha vyhnúť. Viac informácií o bezpečnosti vlákien pomocou zámkov v prostredí Java nájdete v našom java.util.concurrent.Locks článok.

Celý kód tohto tutoriálu je k dispozícii na GitHub.