Programování

JavaBeans: vlastnosti, události a bezpečnost vláken

Java je dynamický jazyk, který zahrnuje snadno použitelné konstrukce jazyků s více vlákny a podpůrné třídy. Mnoho programů Java spoléhá na vícevláknové zpracování, aby využilo paralelismu interních aplikací, zlepšilo výkon sítě nebo urychlilo zpětnou vazbu od uživatelů. Většina běhových časů Java používá multithreading k implementaci funkce uvolňování paměti Java. A konečně, AWT také spoléhá na samostatná vlákna pro své fungování. Stručně řečeno, i ty nejjednodušší programy Java se rodí v prostředí aktivního multithreadingu.

Java fazole, proto, jsou také nasazeny v takovém dynamickém prostředí s více vlákny, a zde spočívá klasické nebezpečí setkání podmínky závodu. Podmínky závodu jsou scénáře toku programu závislé na načasování, které mohou vést k poškození stavu (data programu). V následující části popíšu dva takové scénáře. Každý objekt Java bean musí být navržen s ohledem na podmínky závodu, aby objekt bean vydržel současné použití několika klientskými vlákny.

Problémy s více vlákny s jednoduchými vlastnostmi

Implementace fazole musí předpokládat, že více vláken přistupuje a / nebo upravuje jednu instanci fazole současně. Jako příklad nesprávně implementovaného objektu bean (protože se týká povědomí o více vláknech) zvažte následující objekt BrokenProperties a jeho přidružený testovací program MTProperties:

BrokenProperties.java

importovat java.awt.Point;

// Demo Bean, který nechrání před použitím více vláken.

public class BrokenProperties rozšiřuje Point {

// ------------------------------------------------ ------------------- // set () / get () pro vlastnost 'Spot' // --------------- -------------------------------------------------- -

public void setSpot (Point point) {// 'spot' setter this.x = point.x; this.y = point.y;

} public Point getSpot () {// 'spot' getter to vrátí; }} // Konec Bean / Třída BrokenProperties

MTProperties.java

importovat java.awt.Point; nástroje pro import. *; import utility.beans. *;

veřejná třída MTProperties rozšiřuje vlákno {

chráněné BrokenProperties myBean; // cílová fazole na bash ..

chráněný int myID; // každé vlákno nese trochu ID

// ------------------------------------------------ ------------------- // hlavní () vstupní bod // ---------------------- --------------------------------------------- public static void main ( Řetězec [] args) {

BrokenProperties fazole; Závit nitě;

bean = (BrokenProperties) BeansKit.newBean ("BrokenProperties");

for (int i = 0; i <20; i ++) {// start 20 threads to bash bean thread = new MTProperties (bean, i); // vlákna získají přístup k objektu bean thread.start (); }} // ---------------------------------------------- --------------------- // MTProperties Constructor // ----------------------- --------------------------------------------

public MTProperties (BrokenProperties bean, int id) {this.myBean = bean; // všimněte si fazole, kterou chcete adresovat. mojeID = id; // všimněte si, kdo jsme} // ----------------------------------------- -------------------------- // hlavní smyčka vlákna: // dělat navždy // vytvořit nový náhodný bod pomocí x == y // řekněte fazole, aby přijala Point jako svou novou vlastnost 'spot' // zeptejte se fazole, jaká je nyní vlastnost 'spot' nastavena na // házejte vratce, pokud se spot x nerovná spotu y // --------- -------------------------------------------------- -------- public void run () {int someInt; Bodový bod = nový Point ();

while (true) {someInt = (int) (Math.random () * 100); point.x = someInt; point.y = someInt; myBean.setSpot (bod);

point = myBean.getSpot (); if (point.x! = point.y) {System.out.println ("Poškozený fazol! x =" + point.x + ", y =" + point.y); System.exit (10); } System.out.print ((char) ('A' + myID)); System.out.flush (); }}} // Konec třídy MTProperties

Poznámka: Balíček nástrojů importovaný uživatelem MTPvlastnosti obsahuje opakovaně použitelné třídy a statické metody vyvinuté pro knihu autorem.

Výše uvedené dva zdrojové kódy definují fazole s názvem BrokenProperties a třídu MTPvlastnosti, který se používá k procvičení fazole z 20 běžících vláken. Pojďme následovat MTPvlastnosti' hlavní() vstupní bod: Nejprve vytvoří instanci fazole BrokenProperties, následuje vytvoření a spuštění 20 vláken. Třída MTPvlastnosti rozšiřuje java.lang.Thread, takže vše, co musíme udělat, abychom změnili třídu MTPvlastnosti do vlákna je přepsat třídu Vláknoje běh() metoda. Konstruktor pro naše vlákna má dva argumenty: objekt fazole, se kterým bude vlákno komunikovat, a jedinečnou identifikaci, která umožňuje snadné rozlišení 20 vláken za běhu.

Obchodní konec této ukázky je náš běh() metoda ve třídě MTPvlastnosti. Zde provádíme smyčky navždy a vytváříme náhodné nové (x, y) body, ale s následující charakteristikou: jejich souřadnice x se vždy rovná jejich souřadnici y. Tyto náhodné body jsou předány fazole setSpot () metodu setter a poté okamžitě přečíst zpět pomocí getSpot () getrova metoda. Čekali byste čtení bod vlastnost shodná s náhodným bodem vytvořeným před několika milisekundami. Tady je ukázkový výstup programu při vyvolání na příkazovém řádku:

ABBBBBBBBBBBBBBBBBBDJJJJJJJJJJJJJJJJJJJJEGHHHHHHHHHHHHHHHHHHSSSSSSSSSSSSSS IICCBBBBBBBBBBBBBBBBBKBDLJMBOPLQNRTPHHHHHHHHFFFFFFFFFFFFSSSSSSSSSSSSSSSSSS FFFFFFFFFFFFFFFAAAAAACCCCCCCKKKKKKKKKKKKKKKKKMMMMMMMMMMMMMMMMMMMMMMMMMMMDD JEOQQQQQQQQQQQQQQQRRRRRRRRRRRRRRRRRBBBBBBBBBBBBBBBTTTTTTTTTTTTTTTTLPPPPPPP PPPPGGHHHFFFFFFFFIIIIIIIIIIIIIISSSSSSSSSSSSSSSSSSSSACCCCCCCCCCCCCCCCCCCKMD QQQQQQNNNNNNNNNNNNNNNNRRRRRTRRHHHHHHHHHFFFFFFFFFFFFFFFFFFIIIIIIIIIIIIIIIII MMMJEEEEEEEEEEDDDDEEEEEEEEEOOOOOOOOOOOOOOOOOOOOOOOOOOOQNNNNNNNNBTLPLRGFFFF FFFFFFFFFIIAAAAAAAAAAAAAAAAASSSSSSSSSSSSSSSSSSKKKKKKKKKKKKKKKKCCCCCCCMMJAA AACBean poškozen! x = 67, y = 13 OOOOOOOOOOOOOOOOOOOOO 

Výstup ukazuje 20 podprocesů běžících paralelně (pokud jde o lidský pozorovatel); každé vlákno používá k tisku jednoho z písmen ID, které obdrželo v době výstavby A na T, prvních 20 písmen abecedy. Jakmile jakékoli vlákno zjistí, že čtení zpět bod vlastnost neodpovídá naprogramované charakteristice x = y, podproces vytiskne zprávu "Bean poškozen" a zastaví experiment.

To, co vidíte, je vedlejší stav, který poškozuje stav závodu v fazole setSpot () kód. Zde je tato metoda znovu:

public void setSpot (Point point) {// 'spot' setter this.x = point.x; this.y = point.y; } 

Co by se mohlo pokazit v tak jednoduchém kousku kódu? Představit si závit A povolání setSpot () s bodovým argumentem rovným (67,67). Pokud nyní zpomalíme hodiny vesmíru, abychom viděli, jak virtuální stroj Java (JVM) provádí každý příkaz Java, jeden po druhém, můžeme si představit závit A provedení příkazu x coordinate copy (this.x = point.x;) a pak najednou závit A zamrzne operační systém a nit C. je naplánováno na chvíli běžet. Ve svém předchozím provozním stavu nit C. právě vytvořil svůj vlastní nový náhodný bod (13,13), tzv setSpot () sám, a pak se zmrazil, aby vytvořil prostor pro závit M, hned poté, co nastavila souřadnici x na 13. Takže pokračování nit C. nyní pokračuje ve své naprogramované logice: nastavení y na 13 a kontrola, zda je vlastnost spot stejná (13, 13), ale zjistí, že se záhadně změnila na nelegální stav (67, 13); souřadnice x je poloviční oproti tomu, co závit A bylo nastavení bod a souřadnice y je poloviční oproti tomu, co nit C.nastavilbod na. Konečným výsledkem je, že fazole BrokenProperties skončí s interně nekonzistentním stavem: rozbitá vlastnost.

Kdykoli ne-atomový datovou strukturu (tj. strukturu skládající se z více než jedné části) lze upravit více než jedním vláknem najednou, musíte chránit strukturu pomocí zámku. V Javě se to provádí pomocí synchronizované klíčové slovo.

Varování: Na rozdíl od všech ostatních typů Java si to všimněte, že to Java nezaručuje dlouho a dvojnásobek jsou zpracovávány atomově! To je proto, že dlouho a dvojnásobek vyžadují 64 bitů, což je dvojnásobná délka délky slov většiny architektur CPU (32 bitů). Načítání i ukládání jednotlivých slov stroje jsou skutečně atomické operace, ale přesun 64bitových entit vyžaduje dva takové pohyby, které nejsou chráněny Javou z obvyklého důvodu: výkon. (Některé CPU umožňují, aby byla systémová sběrnice uzamčena k provádění víceslovných přenosů atomově, ale toto zařízení není k dispozici na všech CPU a v každém případě by bylo neuvěřitelně nákladné aplikovat na všechny dlouho nebo dvojnásobek manipulace!) Takže i když vlastnost sestává pouze z jedné dlouho nebo singl dvojnásobek, měli byste použít opatření úplného uzamčení, abyste ochránili vaše touhy nebo zdvojnásobení před náhlým úplným poškozením.

The synchronizované klíčové slovo označuje blok kódu jako atomový krok. Kód nelze „rozdělit“, jako když jiný podproces přeruší kód, aby potenciálně znovu vstoupil do daného bloku (odtud výraz reentrantní kód; veškerý kód Java by měl být znovu zadán). Řešení pro naši fazole BrokenProperties je triviální: stačí ji vyměnit setSpot () metoda s tímto:

public void setSpot (Point point) {// 'spot' setter synchronized (this) {this.x = point.x; this.y = point.y; }} 

Nebo alternativně s tímto:

public synchronized void setSpot (Point point) {// 'spot' setter this.x = point.x; this.y = point.y; } 

Obě náhrady jsou naprosto ekvivalentní, i když dávám přednost prvnímu stylu, protože jasněji ukazuje, jaká je přesná funkce synchronizované klíčové slovo je: synchronizovaný blok je vždy propojen s objektem, který se uzamkne. Podle uzamčeno Myslím tím, že JVM se nejprve pokusí získat zámek (tj. Výhradní přístup) k objektu (tj. Získat k němu výhradní přístup), nebo čeká, až se objekt odemkne, pokud byl uzamčen jiným vláknem. Proces zamykání zaručuje, že jakýkoli objekt může být uzamčen (nebo vlastněn) pouze jedním vláknem najednou.

Takže synchronizováno (toto) syntaxe jasně odráží vnitřní mechanismus: Argument uvnitř závorek je objekt, který má být uzamčen (aktuální objekt) před zadáním bloku kódu. Alternativní syntaxe, kde synchronizované klíčové slovo se používá jako modifikátor v podpisu metody, je to prostě zkrácená verze prvního.

Varování: Když jsou označeny statické metody synchronizované, tady není žádný tento předmět k uzamčení; k aktuálnímu objektu jsou přidruženy pouze metody instance. Když jsou tedy metody třídy synchronizovány, java.lang.Class místo toho se k uzamčení použije objekt odpovídající třídě metody. Tento přístup má závažné důsledky pro výkon, protože kolekce instancí třídy sdílí jeden přidružený Třída objekt; kdykoli to Třída objekt se uzamkne, všechny objekty této třídy (ať už 3, 50 nebo 1000!) mají zakázáno vyvolání stejné statické metody. S ohledem na to byste měli před použitím synchronizace se statickými metodami dvakrát přemýšlet.

V praxi si vždy pamatujte explicitní synchronizovaný formulář, protože vám umožňuje „atomizovat“ nejmenší možný blok kódu v rámci metody. Zkratková forma „atomizuje“ celou metodu, což je z výkonnostních důvodů často ne co chceš. Jakmile vlákno vstoupilo do atomového bloku kódu, žádné další vlákno, které je třeba provést žádný synchronizovaný kód na stejném objektu tak může učinit.

Spropitné: Když je na objektu získán zámek, pak Všechno synchronizovaný kód pro třídu daného objektu bude atomický. Proto pokud vaše třída obsahuje více než jednu datovou strukturu, s kterou je třeba zacházet atomicky, ale tyto datové struktury jsou jinak nezávislý navzájem, pak může nastat další úzké místo výkonu. Klienti volající synchronizované metody, které manipulují s jednou interní datovou strukturou, zablokují všechny ostatní klienty, kteří volají jiné metody, které se zabývají jinými atomovými datovými strukturami vaší třídy. Je zřejmé, že byste se měli těmto situacím vyhnout rozdělením třídy na menší třídy, které zpracovávají pouze jednu datovou strukturu, která má být zpracována atomicky najednou.

JVM implementuje svou synchronizační funkci vytvářením front vláken čekajících na odemčení objektu. I když je tato strategie skvělá, pokud jde o ochranu konzistence složených datových struktur, může mít za následek vícevláknové dopravní zácpy, když je méně než efektivní část kódu označena jako synchronizované.

Proto vždy věnujte pozornost tomu, kolik kódu synchronizujete: mělo by to být nezbytné minimum. Představte si například naši setSpot () metoda původně sestávala z:

public void setSpot (bodový bod) {// 'spot' setter log.println ("setSpot () vyvolaný" + this.toString ()); this.x = point.x; this.y = point.y; } 

Ačkoliv tisk prohlášení může logicky patří do setSpot () Metoda není součástí posloupnosti příkazů, kterou je třeba seskupit do atomového celku. Proto je v tomto případě správný způsob použití synchronizované klíčové slovo by bylo následující:

public void setSpot (bodový bod) {// 'spot' setter log.println ("setSpot () vyvolaný" + this.toString ()); synchronized (this) {this.x = point.x; this.y = point.y; }} 

„Líný“ způsob a přístup, kterému byste se měli vyhnout, vypadá takto:

$config[zx-auto] not found$config[zx-overlay] not found