Úvod do synchronizace v Javě

Synchronizace je funkce Java, která omezuje více vláken ve snaze získat přístup k běžně sdíleným prostředkům současně. Zde sdílené prostředky odkazují na obsah externích souborů, proměnné třídy nebo záznamy databáze.

Synchronizace je široce používána ve vícevláknovém programování. „Synchronizované“ je klíčové slovo, které poskytuje vašemu kódu schopnost umožnit na něm pracovat pouze jednom vláknu bez rušení jiným vláknem během tohoto období.

Proč potřebujeme synchronizaci v Javě?

  • Java je vícevláknový programovací jazyk. To znamená, že k dokončení úkolu mohou běžet současně dvě nebo více vláken. Když vlákna běží současně, existuje velká šance na scénář, ve kterém by váš kód mohl poskytnout neočekávané výsledky.
  • Možná se divíte, že pokud multithreading může způsobit chybné výstupy, tak proč je považován za důležitou funkci v Javě?
  • Díky multithreadingu je váš kód rychlejší tím, že paralelně provozujete více podprocesů, čímž se zkracuje doba provádění kódu a poskytuje vysoký výkon. Použití prostředí s více vlákny však vede k nepřesným výstupům v důsledku stavu, který je běžně známý jako stav rasy.

Co je to podmínka závodu?

Pokud jsou dva nebo více podprocesů spuštěny paralelně, mají v tomto okamžiku tendenci přistupovat a upravovat sdílené prostředky. O sekvencích, ve kterých jsou podprocesy spuštěny, rozhoduje algoritmus plánování podprocesů.

Z tohoto důvodu nelze předpovídat pořadí, ve kterém budou vlákna prováděna, protože je řízena pouze plánovačem vláken. To má vliv na výstup kódu a výsledkem jsou nekonzistentní výstupy. Protože více závodů spolu závodí, aby se dokončila operace, je tato podmínka označována jako „závodní stav“.

Uvažujme například následující kód:

Class Modify:
package JavaConcepts;
public class Modify implements Runnable(
private int myVar=0;
public int getMyVar() (
return myVar;
)
public void setMyVar(int myVar) (
this.myVar = myVar;
)
public void increment() (
myVar++;
)
@Override
public void run() (
// TODO Auto-generated method stub
this.increment();
System.out.println("Current thread being executed "+ Thread.currentThread().getName() + "Current Thread value " + this.getMyVar());
)
)
Class RaceCondition:
package JavaConcepts;
public class RaceCondition (
public static void main(String() args) (
Modify mObj = new Modify();
Thread t1 = new Thread(mObj, "thread 1");
Thread t2 = new Thread(mObj, "thread 2");
Thread t3 = new Thread(mObj, "thread 3");
t1.start();
t2.start();
t3.start();
)
)

Při následném spuštění výše uvedeného kódu budou výstupy následující:

Ourput1:

Probíhá aktuální vlákno 1 Aktuální hodnota vlákna 3

Probíhá aktuální podproces vlákno 3 Hodnota aktuálního vlákna 2

Probíhá aktuální vlákno vlákno 2 Hodnota aktuálního vlákna 3

Výstup2:

Probíhá aktuální vlákno vlákno 3 Hodnota aktuální vlákno 3

Probíhá aktuální vlákno vlákno 2 Hodnota aktuálního vlákna 3

Probíhá aktuální vlákno 1 Aktuální hodnota vlákna 3

Výstup 3:

Probíhá aktuální vlákno vlákno 2 Hodnota aktuálního vlákna 3

Probíhá aktuální vlákno 1 Aktuální hodnota vlákna 3

Probíhá aktuální vlákno vlákno 3 Hodnota aktuální vlákno 3

Výstup4:

Probíhá aktuální vlákno vlákno 1 Hodnota aktuální vlákno 2

Probíhá aktuální vlákno vlákno 3 Hodnota aktuální vlákno 3

Probíhá aktuální vlákno vlákno 2 Hodnota aktuálního vlákna 2

  • Z výše uvedeného příkladu můžete usoudit, že vlákna jsou spouštěna náhodně a že hodnota je také nesprávná. Podle naší logiky by hodnota měla být zvýšena o 1. Avšak zde je výstupní hodnota ve většině případů 3 a v několika případech je to 2.
  • Proměnná „myVar“ je zde sdílený prostředek, na kterém spouští více vláken. Vlákna přistupují a upravují hodnotu „myVar“ současně. Podívejme se, co se stane, pokud komentujeme další dvě vlákna.

Výstupem v tomto případě je:

Aktuální vlákno je prováděno vlákno 1 Hodnota aktuálního vlákna 1

To znamená, že když běží jedno vlákno, výstup je očekáván. Pokud je však spuštěno více podprocesů, hodnota je upravována každým vláknem. Proto je třeba omezit počet vláken pracujících na sdíleném prostředku na jedno vlákno současně. Toho je dosaženo pomocí synchronizace.

Porozumění synchronizaci v jazyce Java

  • Synchronizace v Javě je dosahována pomocí klíčového slova „synchronized“. Toto klíčové slovo lze použít pro metody nebo bloky nebo objekty, ale nelze je použít s třídami a proměnnými. Synchronizovaný kus kódu umožňuje přístup a úpravy v jednom okamžiku pouze jednom vláknu.
  • Synchronizovaný kus kódu však ovlivňuje výkon kódu, protože zvyšuje čekací dobu ostatních vláken, které se k němu pokoušejí dostat. Část kódu by tedy měla být synchronizována pouze v případě, že existuje šance, že dojde ke stavu závodu. Pokud tomu tak není, měl by se tomu vyhnout.

Jak interně funguje synchronizace v jazyce Java?

  • Interní synchronizace v Javě byla implementována pomocí koncepce zámku (známé také jako monitor). Každý objekt Java má svůj vlastní zámek. V synchronizovaném bloku kódu musí vlákno získat zámek, než bude moci provést tento konkrétní blok kódu. Jakmile vlákno získá zámek, může provést tento kus kódu.
  • Po dokončení provádění se zámek automaticky uvolní. Pokud pro synchronizovaný kód vyžaduje další vlákno, čeká na uvolnění zámku aktuální vlákno, které na něm pracuje. Tento proces získávání a uvolňování zámků je interně zajišťován virtuálním strojem Java. Program nenese odpovědnost za získání a uvolnění zámků pomocí vlákna. Zbývající podprocesy však mohou provádět jakýkoli jiný nesynchronizovaný kus kódu současně.

Synchronizujme náš předchozí příklad synchronizací kódu uvnitř metody run pomocí synchronizovaného bloku ve třídě „Modify“, jak je uvedeno níže:

Class Modify:
package JavaConcepts;
public class Modify implements Runnable(
private int myVar=0;
public int getMyVar() (
return myVar;
)
public void setMyVar(int myVar) (
this.myVar = myVar;
)
public void increment() (
myVar++;
)
@Override
public void run() (
// TODO Auto-generated method stub
synchronized(this) (
this.increment();
System.out.println("Current thread being executed "
+ Thread.currentThread().getName() + " Current Thread value " + this.getMyVar());
)
)
)

Kód třídy „RaceCondition“ zůstává stejný. Nyní při spuštění kódu je výstup následující:

Výstup1:

Aktuální vlákno je prováděno vlákno 1 Hodnota aktuálního vlákna 1

Aktuální vlákno je prováděno vlákno 2 Hodnota aktuálního vlákna 2

Aktuální podproces je prováděn podproces 3 Aktuální hodnota podprocesu 3

Výstup2:

Aktuální vlákno je prováděno vlákno 1 Hodnota aktuálního vlákna 1

Aktuální vlákno je prováděno vlákno 3 Hodnota aktuálního vlákna 2

Aktuální vlákno je prováděno vlákno 2 Hodnota aktuálního vlákna 3

Všimněte si, že náš kód poskytuje očekávaný výstup. Zde každé vlákno zvyšuje hodnotu o 1 pro proměnnou „myVar“ (ve třídě „Upravit“).

Poznámka: Synchronizace je nutná, když na stejném objektu pracuje více vláken. Pokud více podprocesů pracuje na více objektech, synchronizace není nutná.

Například upravme kód ve třídě „RaceCondition“, jak je uvedeno níže, a pracujeme s dříve nesynchronizovanou třídou „Upravit“.

package JavaConcepts;
public class RaceCondition (
public static void main(String() args) (
Modify mObj = new Modify();
Modify mObj1 = new Modify();
Modify mObj2 = new Modify();
Thread t1 = new Thread(mObj, "thread 1");
Thread t2 = new Thread(mObj1, "thread 2");
Thread t3 = new Thread(mObj2, "thread 3");
t1.start();
t2.start();
t3.start();
)
)

Výstup:

Aktuální vlákno je prováděno vlákno 1 Hodnota aktuálního vlákna 1

Aktuální vlákno je prováděno vlákno 2 Hodnota aktuálního vlákna 1

Aktuální vlákno je prováděno vlákno 3 Hodnota aktuálního vlákna 1

Typy synchronizace v Javě:

Existují dva typy synchronizace vláken, jeden se vzájemně vylučují a druhý mezi vláknovou komunikací.

1.Mutually Exclusive

  • Synchronizovaná metoda.
  • Statická synchronizovaná metoda
  • Synchronizovaný blok.

2. Koordinace vláken (komunikace mezi vlákny v javě)

Vzájemně se vylučující:

  • V tomto případě podprocesy získají zámek před operací s objektem, čímž se vyhnou práci s objekty, jejichž hodnoty byly zpracovány jinými vlákny.
  • Toho lze dosáhnout třemi způsoby:

i. Synchronizovaná metoda: Pro metodu můžeme použít klíčové slovo „synchronizované“, čímž se z ní stane synchronizovaná metoda. Každé vlákno, které vyvolá synchronizovanou metodu, získá zámek pro tento objekt a uvolní jej po dokončení jeho operace. Ve výše uvedeném příkladu můžeme naši metodu „run ()“ synchronizovat pomocí klíčového slova „synchronized“ po modifikátoru přístupu.

@Override
public synchronized void run() (
// TODO Auto-generated method stub
this.increment();
System.out.println("Current thread being executed "
+ Thread.currentThread().getName() + " Current Thread value " + this.getMyVar());
)

Výstupem pro tento případ bude:

Aktuální vlákno je prováděno vlákno 1 Hodnota aktuálního vlákna 1

Aktuální vlákno je prováděno vlákno 3 Hodnota aktuálního vlákna 2

Aktuální vlákno je prováděno vlákno 2 Hodnota aktuálního vlákna 3

ii. Statická synchronizovaná metoda: K synchronizaci statických metod je třeba získat zámek na úrovni třídy. Poté, co vlákno získá zámek úrovně třídy, pak bude moci provést statickou metodu. Zatímco vlákno drží zámek úrovně třídy, žádné jiné vlákno nemůže provádět žádnou jinou statickou synchronizovanou metodu této třídy. Ostatní vlákna však mohou provádět jakoukoli jinou běžnou metodu nebo pravidelnou statickou metodu nebo dokonce nestatickou synchronizovanou metodu této třídy.

Uvažujme například naši třídu „Upravit“ a provedeme její změny převedením naší metody „přírůstku“ na statickou synchronizovanou metodu. Změny kódu jsou následující:

package JavaConcepts;
public class Modify implements Runnable(
private static int myVar=0;
public int getMyVar() (
return myVar;
)
public void setMyVar(int myVar) (
this.myVar = myVar;
)
public static synchronized void increment() (
myVar++;
System.out.println("Current thread being executed " + Thread.currentThread().getName() + " Current Thread value " + myVar);
)
@Override
public void run() (
// TODO Auto-generated method stub
increment();
)
)

iii. Synchronizovaný blok: Jednou z hlavních nevýhod synchronizované metody je to, že zvyšuje čekací dobu vláken ovlivňující výkon kódu. Proto, aby bylo možné synchronizovat pouze požadované řádky kódu místo celé metody, je třeba použít synchronizovaný blok. Použití synchronizovaného bloku snižuje čekací dobu vláken a zvyšuje také výkon. V předchozím příkladu jsme již použili synchronizovaný blok při první synchronizaci našeho kódu.

Příklad:
public void run() (
// TODO Auto-generated method stub
synchronized(this) (
this.increment();
System.out.println("Current thread being executed "
+ Thread.currentThread().getName() + " Current Thread value " + this.getMyVar());
)
)

Koordinace vláken:

U synchronizovaných vláken je komunikace mezi vlákny důležitým úkolem. Vestavěné metody, které pomáhají dosáhnout komunikace mezi vlákny pro synchronizovaný kód, jsou zejména:

  • Počkejte()
  • oznámit()
  • oznámit vše ()

Poznámka: Tyto metody patří do třídy objektů, nikoli do třídy vláken. Aby vlákno mohlo vyvolat tyto metody na objektu, mělo by to držet zámek na tomto objektu. Tyto metody také způsobí, že vlákno uvolní zámek na objektu, na který je vyvoláno.

wait (): vlákno při vyvolání metody wait (), uvolní zámek na objektu a přejde do čekacího stavu. Má dvě přetížení metod:

  • veřejné konečné neplatné čekání () vyvolá InterruptedException
  • veřejné konečné neplatné čekání (dlouhý časový limit) vyvolá přerušenou výjimku
  • veřejné konečné neplatné čekání (dlouhý časový limit, int nanos) vyvolá InterruptedException

notify (): Vlákno vyšle signál do jiného vlákna ve vyčkávacím stavu pomocí metody Notify (). Oznámení odešle pouze jednomu vláknu, takže toto vlákno může obnovit jeho provádění. Které vlákno obdrží oznámení mezi všemi vlákny ve stavu čekání, závisí na Java Virtual Machine.

  • veřejné konečné neplatné oznámení ()

notifyAll (): Když vlákno vyvolá metodu announAll (), bude oznámeno každé vlákno v čekajícím stavu. Tato vlákna budou prováděna jeden po druhém na základě pořadí, o kterém rozhodl Java Virtual Machine.

  • veřejné konečné neplatné oznámeníVšechny ()

Závěr

V tomto článku jsme viděli, jak práce v prostředí s více vlákny může vést k nekonzistenci dat v důsledku stavu závodu. Jak nám to synchronizace pomůže překonat omezením jediného vlákna pro práci na sdíleném prostředku současně. Také, jak synchronizovaná vlákna spolu komunikují.

Doporučené články:

Toto byl průvodce Co je to synchronizace v Javě ?. Zde diskutujeme úvod, porozumění, potřebu, práci a typy synchronizace s ukázkovým kódem. Další informace naleznete také v dalších navrhovaných článcích -

  1. Serializace v Javě
  2. Co je to Generics v Javě?
  3. Co je API v Javě?
  4. Co je binární strom v Javě?
  5. Příklady a jak generici pracují v C #

Kategorie: