Programovanie

JavaBeans: vlastnosti, udalosti a bezpečnosť vlákien

Java je dynamický jazyk, ktorý obsahuje ľahko použiteľné jazykové konštrukcie s viacerými vláknami a podporné triedy. Mnoho programov Java sa spolieha na viacvláknové spracovanie, aby využilo paralelnosť interných aplikácií, zlepšilo výkon siete a urýchlilo reakciu používateľov. Väčšina prevádzkových časov Javy používa na implementáciu funkcie Java na zhromažďovanie odpadkov viacvláknové spracovanie. Nakoniec sa AWT pri svojej činnosti spolieha aj na samostatné vlákna. Stručne povedané, aj tie najjednoduchšie Java programy sa rodia v prostredí aktívneho multithreadingu.

Java fazule sú preto nasadené aj v takom dynamickom prostredí s viacerými vláknami a v tomto prípade spočíva klasické nebezpečenstvo stretnutia závodné podmienky. Podmienky pretekov sú scenáre toku programu závislé od načasovania, ktoré môžu viesť k poškodeniu stavu (údaje programu). V nasledujúcej časti podrobne popíšem dva takéto scenáre. Každá fazuľa Java musí byť navrhnutá s ohľadom na závodné podmienky, aby fazuľa vydržala súčasné použitie niekoľkými vláknami klienta.

Problémy s multithreadingom s jednoduchými vlastnosťami

Implementácie fazule musia predpokladať, že viac vlákien pristupuje a / alebo upravuje jednu inštanciu fazule naraz. Ako príklad nesprávne implementovanej fazule (týka sa to povedomia o viacerých vláknach) zvážte nasledujúcu fazuľu BrokenProperties a jej pridružený testovací program MTProperties:

BrokenProperties.java

import java.awt.Point;

// Demo Bean, ktorý nezabráni použitiu viacerých vlákien.

public class BrokenProperties rozširuje Point {

// ------------------------------------------------ ------------------- // set () / get () pre vlastnosť „Spot“ // --------------- -------------------------------------------------- -

public void setSpot (bodový bod) {// 'spotový' setter this.x = point.x; this.y = point.y;

} public Point getSpot () {// 'spot' getter to vráti; }} // Koniec Bean / triedy BrokenProperties

MTProperties.java

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

verejná trieda MTProperties rozširuje vlákno {

chránené BrokenProperties myBean; // cieľová fazuľa do bash ..

chránený int myID; // každé vlákno nesie kúsok ID

// ------------------------------------------------ ------------------- // hlavný () vstupný bod // ---------------------- --------------------------------------------- public static void main main ( Reťazec [] args) {

Fazuľa BrokenProperties; Závit nite;

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

for (int i = 0; i <20; i ++) {// spustí 20 vlákien na bash bean vlákno = nové MTProperties (bean, i); // vlákna získajú prístup k fazuľovému vláknu.start (); }} // ---------------------------------------------- --------------------- // MTProperties Constructor // ----------------------- --------------------------------------------

verejné MTProperties (BrokenProperties bean, int id) {this.myBean = bean; // všimnite si fazuľu, ktorú chcete adresovať. mojeID = id; // všimnite si, kto sme} // ----------------------------------------- -------------------------- // hlavná slučka vlákna: // robiť navždy // vytvoriť nový náhodný bod pomocou x == y // povedzte fazuli, aby prijala Point ako svoju novú vlastnosť „spot“ // opýtajte sa fazule, aká vlastnosť „spotu“ je teraz nastavená na // vrhajte vratko, ak sa spot x nerovná spotu y // --------- -------------------------------------------------- -------- public void run () {int someInt; Bodový bod = nový Bod ();

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 ("Bean poškodený! x =" + point.x + ", y =" + point.y); System.exit (10); } System.out.print ((char) ('A' + myID)); System.out.flush (); }}} // Koniec triedy MTPvlastnosti

Poznámka: Balík nástrojov importovaný z MTPvlastnosti obsahuje opakovane použiteľné triedy a statické metódy vyvinuté pre knihu autorom.

Dva vyššie uvedené zoznamy zdrojových kódov definujú fazuľa s názvom BrokenProperties a triedu MTPvlastnosti, ktorý sa používa na precvičenie fazule z 20 bežiacich vlákien. Nasledujme nás MTPvlastnosti' hlavný() vstupný bod: Najprv vytvorí inštanciu fazule BrokenProperties, po ktorej nasleduje vytvorenie a spustenie 20 vlákien. Trieda MTPvlastnosti predlžuje java.lang.Thread, takže všetko, čo musíme urobiť, aby sme zmenili triedu MTPvlastnosti do vlákna je prepísať triedu Závitje run () metóda. Konštruktor pre naše vlákna má dva argumenty: objekt fazule, s ktorým bude vlákno komunikovať, a jedinečnú identifikáciu, ktorá umožňuje ľahké rozlíšenie 20 vlákien za behu.

Obchodný koniec tejto ukážky je náš run () metóda v triede MTPvlastnosti. Tu prechádzame večne a vytvárame náhodné nové (x, y) body, ale s nasledujúcou charakteristikou: ich súradnica x sa vždy rovná ich súradnici y. Tieto náhodné body sa odovzdajú fazuli setSpot () a potom okamžite načítať pomocou getSpot () getrova metóda. Čakali by ste čítanie miesto vlastnosť zhodná s náhodným bodom vytvoreným pred niekoľkými milisekundami. Tu je ukážkový výstup programu pri vyvolaní z príkazového riadku:

ABBBBBBBBBBBBBBBBBBDJJJJJJJJJJJJJJJJJJJJEGHHHHHHHHHHHHHHHHHHSSSSSSSSSSSSSS IICCBBBBBBBBBBBBBBBBBKBDLJMBOPLQNRTPHHHHHHHHFFFFFFFFFFFFSSSSSSSSSSSSSSSSSS FFFFFFFFFFFFFFFAAAAAACCCCCCCKKKKKKKKKKKKKKKKKMMMMMMMMMMMMMMMMMMMMMMMMMMMDD JEOQQQQQQQQQQQQQQQRRRRRRRRRRRRRRRRRBBBBBBBBBBBBBBBTTTTTTTTTTTTTTTTLPPPPPPP PPPPGGHHHFFFFFFFFIIIIIIIIIIIIIISSSSSSSSSSSSSSSSSSSSACCCCCCCCCCCCCCCCCCCKMD QQQQQQNNNNNNNNNNNNNNNNRRRRRTRRHHHHHHHHHFFFFFFFFFFFFFFFFFFIIIIIIIIIIIIIIIII MMMJEEEEEEEEEEDDDDEEEEEEEEEOOOOOOOOOOOOOOOOOOOOOOOOOOOQNNNNNNNNBTLPLRGFFFF FFFFFFFFFIIAAAAAAAAAAAAAAAAASSSSSSSSSSSSSSSSSSKKKKKKKKKKKKKKKKCCCCCCCMMJAA AACBean poškodený! x = 67, r = 13 OOOOOOOOOOOOOOOOOOOOO 

Výstup ukazuje 20 vlákien bežiacich paralelne (pokiaľ ide o ľudského pozorovateľa); každé vlákno používa na vytlačenie jedného z písmen ID, ktoré získalo v čase stavby A do T, prvých 20 písmen abecedy. Hneď ako akékoľvek vlákno zistí, že čítanie je späť miesto vlastnosť nezodpovedá naprogramovanej charakteristike x = y, vlákno vytlačí správu „Bean poškodený“ a zastaví experiment.

To, čo vidíte, je vedľajší účinok stavu rasy v fazuli na narušenie stavu setSpot () kód. Tu je táto metóda znova:

public void setSpot (bodový bod) {// 'spotový' setter this.x = point.x; this.y = point.y; } 

Čo by sa mohlo stať, že sa pokazí taký jednoduchý kúsok kódu? Predstavte si závit A volanie setSpot () s bodovým argumentom rovným (67,67). Ak teraz spomalíme hodiny vesmíru, aby sme videli, ako virtuálny stroj Java (JVM) vykonáva každý príkaz Java, jeden po druhom, môžeme si predstaviť závit A vykonanie príkazu x coordinate copy (this.x = point.x;) a potom zrazu závit A operačný systém zamrzne a závit C je naplánované na chvíľu spustiť. V predchádzajúcom spustenom stave závit C práve vytvoril svoj vlastný nový náhodný bod (13,13), tzv setSpot () sám, a potom zamrzol, aby vytvoril priestor pre závit M, hneď potom, ako nastavila súradnicu x na 13. Takže pokračovanie závit C teraz pokračuje vo svojej naprogramovanej logike: nastavenie y na 13 a kontrola, či je vlastnosť spotu rovnaká (13, 13), ale zistí, že sa záhadne zmenila na nelegálny stav (67, 13); súradnica x je polovica stavu čo závit A bolo nastavenie miesto a y súradnica je polovica stavu čo závit Cmal nastavenýmiesto do. Konečným výsledkom je, že fazuľa BrokenProperties skončí s vnútorne nekonzistentným stavom: rozbitá vlastnosť.

Kedykoľvek a ne-atómový dátovú štruktúru (teda štruktúru pozostávajúcu z viacerých častí) je možné upravovať súčasne viac ako jedným vláknom, musíte štruktúru chrániť pomocou zámku. V Jave sa to deje pomocou synchronizované kľúčové slovo.

Pozor: Na rozdiel od všetkých ostatných typov Java si uvedomte, že Java to nezaručuje dlho a dvojitý sú považované za atómové! To je preto, že dlho a dvojitý vyžadujú 64 bitov, čo je dvakrát viac ako dĺžka slova väčšiny moderných architektúr CPU (32 bitov). Načítanie aj ukladanie jednotlivých strojových slov sú skutočne atómové operácie, ale presun 64-bitových entít vyžaduje dva také pohyby, ktoré z bežného dôvodu nechráni Java. (Niektoré CPU umožňujú uzamknutie systémovej zbernice na vykonávanie viacslovných prenosov atómovo, ale toto zariadenie nie je k dispozícii na všetkých CPU a jeho použitie v každom prípade by bolo neuveriteľne drahé. dlho alebo dvojitý manipulácie!) Takže, aj keď vlastnosť pozostáva iba z jednej dlho alebo slobodný dvojitý, mali by ste použiť všetky bezpečnostné opatrenia na zamknutie, aby ste chránili svoje túžby alebo zdvojnásobenie pred náhlym úplným poškodením.

The synchronizované kľúčové slovo označuje blok kódu ako atómový krok. Kód nie je možné „rozdeliť“, ako keď iné vlákno kód preruší a potenciálne tak znova vstúpi do daného bloku (odtiaľ pochádza výraz reentrantský kód; mali by sa znova zadať všetky kódy Java). Riešenie pre našu fazuľu BrokenProperties je triviálne: stačí ju vymeniť setSpot () metóda s týmto:

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

Alebo alternatívne s týmto:

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

Obe náhrady sú úplne rovnocenné, aj keď uprednostňujem prvý štýl, pretože jasnejšie ukazuje, aká je presná funkcia synchronizované kľúčové slovo je: synchronizovaný blok je vždy prepojený s objektom, ktorý sa uzamkne. Autor: zamknutý Myslím tým, že JVM sa najskôr pokúsi získať zámok (teda výlučný prístup) k objektu (to znamená získať k nemu výlučný prístup), alebo počká, kým sa objekt odblokuje, ak bol uzamknutý iným vláknom. Proces blokovania zaručuje, že akýkoľvek objekt môže byť uzamknutý (alebo vlastnený) iba jedným vláknom súčasne.

Takže synchronizované (toto) syntax jasne odráža vnútorný mechanizmus: Argumentom v zátvorkách je objekt, ktorý sa má uzamknúť (aktuálny objekt) pred zadaním bloku kódu. Alternatívna syntax, kde synchronizované kľúčové slovo sa používa ako modifikátor v signatúre metódy, je to skrátene jeho verzia.

Pozor: Keď sú označené statické metódy synchronizované, nie je toto predmet na uzamknutie; s aktuálnym objektom sú spojené iba inštančné metódy. Takže keď sú synchronizované metódy triedy, java.lang.Class objekt zodpovedajúci triede metódy sa namiesto toho použije na uzamknutie. Tento prístup má vážne dôsledky na výkon, pretože kolekcia inštancií tried zdieľa jedno priradené Trieda predmet; kedykoľvek to Trieda objekt uzamkne, všetky objekty tejto triedy (či už 3, 50 alebo 1 000!) majú zakázané vyvolať rovnakú statickú metódu. V tejto súvislosti by ste si mali synchronizáciu so statickými metódami dobre rozmyslieť.

V praxi si vždy pamätajte explicitný synchronizovaný formulár, pretože vám umožňuje „atomizovať“ čo najmenší blok kódu v rámci metódy. Skrátená forma „atomizuje“ celú metódu, čo je z výkonnostných dôvodov často nie čo chceš. Akonáhle vlákno vstúpi do atómového bloku kódu, už žiadne iné vlákno, ktoré je potrebné vykonať akýkoľvek synchronizovaný kód na rovnakom objekte.

Tip: Keď sa na objekte získa zámok, potom všetko synchronizovaný kód pre triedu daného objektu sa stane atómovým. Preto, ak vaša trieda obsahuje viac ako jednu dátovú štruktúru, s ktorou je potrebné zaobchádzať atómovo, ale tieto dátové štruktúry sú inak nezávislý navzájom môžu vznikať ďalšie úzke miesta. Klienti, ktorí volajú synchronizované metódy, ktoré manipulujú s jednou internou dátovou štruktúrou, zablokujú všetkých ostatných klientov, ktorí volajú ďalšie metódy, ktoré sa zaoberajú akýmikoľvek inými atómovými dátovými štruktúrami vašej triedy. Je zrejmé, že by ste sa takýmto situáciám mali vyhnúť rozdelením triedy na menšie triedy, ktoré spracúvajú iba jednu dátovú štruktúru, aby sa s nimi narábalo súčasne.

JVM implementuje svoju synchronizačnú funkciu vytváraním radov vlákien čakajúcich na odblokovanie objektu. Aj keď je táto stratégia skvelá, pokiaľ ide o ochranu konzistencie zložených dátových štruktúr, môže mať za následok viacvláknové dopravné zápchy, keď je menej účinná časť kódu označená ako synchronizované.

Preto vždy venujte pozornosť tomu, koľko kódu synchronizujete: malo by to byť nevyhnutné minimum. Predstavte si napríklad našu setSpot () metóda pôvodne pozostávala z:

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

Napriek tomu println vyhlásenie by mohlo logicky patrí do setSpot () metóda nie je súčasťou sekvencie príkazov, ktorú je potrebné zoskupiť do atómového celku. Preto je v tomto prípade správnym spôsobom použitie synchronizované kľúčové slovo by bolo nasledovné:

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

„Lenivý“ spôsob a prístup, ktorému by ste sa mali vyhnúť, vyzerá takto:

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