Pred šiestimi mesiacmi som začal sériu článkov o navrhovaní tried a objektov. V tomto mesiaci Dizajnové techniky V tomto stĺpci budem pokračovať v sledovaní princípov návrhu, ktoré sa týkajú bezpečnosti závitov. Tento článok vám povie, čo je bezpečnosť nití, prečo ich potrebujete, kedy ich potrebujete a ako ich získať.
Čo je to bezpečnosť nití?
Bezpečnosť vlákien jednoducho znamená, že polia objektu alebo triedy si vždy zachovávajú platný stav, ako to pozorujú iné objekty a triedy, aj keď sú súčasne využívané viacerými vláknami.
Jedným z prvých pokynov, ktoré som v tomto stĺpci navrhol (pozri „Návrh inicializácie objektu“), je, že by ste mali navrhovať triedy tak, aby si objekty udržiavali platný stav od začiatku svojej životnosti až do konca. Ak budete postupovať podľa tejto rady a vytvoríte objekty, ktorých premenné inštancie sú všetky súkromné a ktorých metódy umožňujú iba správne prechody stavu v týchto premenných inštancie, ste v dobrej kondícii v prostredí s jedným vláknom. Keď sa však vyskytnú ďalšie vlákna, môžete sa dostať do problémov.
Viaceré vlákna môžu znamenať pre váš objekt problémy, pretože často môže byť stav objektu počas vykonávania metódy dočasne neplatný. Ak objekt vyvoláva iba jedno vlákno, vykoná sa vždy iba jedna metóda a každá sa nechá dokončiť pred vyvolaním inej metódy. V prostredí s jedným vláknom bude mať teda každá metóda šancu zabezpečiť, aby sa akýkoľvek dočasne neplatný stav zmenil na platný predtým, ako sa metóda vráti.
Po zavedení viacerých vlákien však môže JVM prerušiť vlákno vykonávajúce jednu metódu, zatiaľ čo premenné inštancie objektu sú stále v dočasne neplatnom stave. Spoločný podnik JVM potom mohol dať šancu vykonať inému vláknu a toto vlákno mohlo zavolať metódu na rovnakom objekte. Celá vaša tvrdá práca na tom, aby boli vaše inštančné premenné súkromné a vaše metódy vykonávali iba platné transformácie stavu, nebude stačiť na to, aby zabránila tomuto druhému vláknu v pozorovaní objektu v neplatnom stave.
Takýto objekt by nebol bezpečný pre vlákna, pretože vo viacvláknovom prostredí by sa objekt mohol poškodiť alebo by sa mohlo stať, že bude mať neplatný stav. Objekt bezpečný pre vlákna je objekt, ktorý si vždy zachováva platný stav, ako ho pozorujú iné triedy a objekty, a to aj v prostredí s viacerými vláknami.
Prečo sa obávať o bezpečnosť nití?
Pri navrhovaní tried a objektov v Jave musíte myslieť na bezpečnosť vlákien z dvoch veľkých dôvodov:
Podpora viacerých vlákien je zabudovaná do jazyka Java a API
- Všetky vlákna vo virtuálnom stroji Java (JVM) zdieľajú rovnakú haldu a oblasť metód
Pretože multithreading je zabudovaný do Javy, je možné, že ktorákoľvek trieda, ktorú nakoniec navrhnete, môže byť súčasne použitá viacerými vláknami. Nemusíte (a nemali by ste) robiť každú triedu, ktorú navrhujete, bezpečnou pre vlákna, pretože bezpečnosť pre vlákna neprichádza zadarmo. Mali by ste však minimálne myslieť si o bezpečnosti vlákien pri každom návrhu triedy Java. Ďalej v tomto článku nájdete diskusiu o nákladoch na bezpečnosť vlákien a pokyny týkajúce sa toho, kedy je treba zaistiť bezpečnú triedu pre vlákna.
Vzhľadom na architektúru JVM sa musíte starať iba o premenné inštancie a triedy, keď sa obávate o bezpečnosť vlákien. Pretože všetky vlákna zdieľajú rovnakú haldu a v halde sú uložené všetky premenné inštancie, môže sa viac vlákien pokúsiť súčasne použiť inštančné premenné toho istého objektu. Rovnako tak, pretože všetky vlákna zdieľajú rovnakú oblasť metódy a v oblasti metódy sú uložené všetky premenné triedy, viaceré vlákna sa môžu pokúsiť súčasne použiť rovnaké premenné triedy. Ak sa rozhodnete zaistiť bezpečnosť triedy s vláknami, vaším cieľom je zaručiť integritu - v prostredí s viacerými vláknami - premenných inštancie a triedy deklarovaných v tejto triede.
Nemusíte sa obávať viacvláknového prístupu k lokálnym premenným, parametrom metódy a návratovým hodnotám, pretože tieto premenné sa nachádzajú v zásobníku Java. V JVM má každé vlákno pridelený svoj vlastný zásobník Java. Žiadne vlákno nemôže vidieť ani používať žiadne lokálne premenné, návratové hodnoty alebo parametre patriace k inému vláknu.
Vzhľadom na štruktúru JVM sú miestne premenné, parametre metódy a návratové hodnoty vo svojej podstate „bezpečné pre vlákna“. Premenné inštancie a premenné triedy však budú bezpečné z hľadiska vlákien, iba ak svoju triedu navrhnete vhodným spôsobom.
RGBColor # 1: Pripravené pre jedno vlákno
Ako príklad triedy, ktorá je nie bezpečné pre vlákna, zvážte RGBColor
triedy, zobrazené nižšie. Inštancie tejto triedy predstavujú farbu uloženú v troch premenných súkromnej inštancie: r
, g
a b
. Vzhľadom na triedu uvedenú nižšie, RGBColor
objekt by začal svoj život v platnom stave a zažil by iba prechody platného stavu od začiatku svojho života do konca - ale iba v prostredí s jedným vláknom.
// V súborových vláknach / ex1 / RGBColor.java // Inštancie tejto triedy NIE sú bezpečné pre vlákna. verejná trieda RGBColor {private int r; súkromný int g; súkromný int b; public RGBColor (int r, int g, int b) {checkRGBVals (r, g, b); this.r = r; this.g = g; this.b = b; } public void setColor (int r, int g, int b) {checkRGBVals (r, g, b); this.r = r; this.g = g; this.b = b; } / ** * vracia farbu v poli troch ints: R, G a B * / public int [] getColor () {int [] retVal = new int [3]; retVal [0] = r; retVal [1] = g; retVal [2] = b; návrat retVal; } public void invert () {r = 255 - r; g = 255 - g; b = 255 - b; } private static void checkRGBVals (int r, int g, int b) {if (r 255 || g 255 || b <0 || b> 255) {throw new IllegalArgumentException (); }}}
Pretože tri inštančné premenné, int
s r
, g
a b
, sú súkromné, jediný spôsob, ako môžu iné triedy a objekty pristupovať alebo ovplyvňovať hodnoty týchto premenných, je cez RGBColor
konštruktor a metódy. Dizajn konštruktéra a metódy zaručujú, že:
RGBColor
Konštruktér dá premenným vždy správne počiatočné hodnotyMetódy
setColor ()
ainvertovať ()
vždy vykoná platné transformácie stavu na týchto premenných- Metóda
getColor ()
vždy vráti platné zobrazenie týchto premenných
Upozorňujeme, že ak sa zlé údaje odovzdajú konštruktérovi alebo setColor ()
metódou, ukončia náhle s InvalidArgumentException
. The checkRGBVals ()
metóda, ktorá vrhá túto výnimku, v skutočnosti definuje, čo to znamená pre RGBColor
objekt, ktorý má byť platný: hodnoty všetkých troch premenných, r
, g
a b
, musí byť medzi 0 a 255 vrátane. Aby bola platná, farba reprezentovaná týmito premennými musí byť navyše najnovšou farbou odovzdanou konštruktoru alebo setColor ()
metódou alebo vyrobenou invertovať ()
metóda.
Ak v prostredí s jedným vláknom vyvoláte setColor ()
a odovzdať modrou farbou RGBColor
objekt bude modrý, keď setColor ()
vracia. Ak sa potom dovoláte getColor ()
na rovnakom objekte dostanete modrú farbu. V spoločnosti s jedným vláknom to sú prípady RGBColor
triedy sú slušne vychovaní.
Vhadzovanie súbežného kľúča do diel
Bohužiaľ, tento šťastný obraz dobre vychovaného RGBColor
objekt môže byť strašidelný, keď do obrazu vstúpia ďalšie vlákna. Vo viacvláknovom prostredí sú inštancie RGBColor
triedy definované vyššie sú náchylné na dva druhy zlého správania: konflikty zápisu / zápisu a konflikty čítania / zápisu.
Konflikty zápisu / zápisu
Predstavte si, že máte dve vlákna, jedno vlákno s názvom „červené“ a ďalšie s názvom „modré“. Obe vlákna sa snažia nastaviť rovnakú farbu RGBColor
objekt: Červená niť sa pokúša nastaviť farbu na červenú; modrá niť sa pokúša nastaviť farbu na modrú.
Obe tieto vlákna sa pokúšajú súčasne zapisovať do inštančných premenných toho istého objektu. Ak plánovač vlákien prekladá tieto dve vlákna správnym spôsobom, tieto dve vlákna sa nechtiac navzájom ovplyvnia, čo spôsobí konflikt zápisu a zápisu. V tomto procese dve vlákna poškodia stav objektu.
The Nesynchronizované RGBColor
applet
Nasledujúci applet s názvom Nesynchronizovaná farba RGBColor, demonštruje jednu postupnosť udalostí, ktoré by mohli viesť k poškodeniu RGBColor
objekt. Červená niť sa nevinne snaží nastaviť farbu na červenú, zatiaľ čo modrá niť sa nevinne snaží nastaviť farbu na modrú. Nakoniec RGBColor
objekt nepredstavuje ani červenú, ani modrú farbu, ale znepokojujúcu farbu, purpurovú.
Postupovať postupnosťou udalostí, ktoré vedú k poškodeniu RGBColor
objektu, stlačte tlačidlo Krok na applete. Stlačením Späť zálohujete krok a obnovením Obnovíte zálohovanie na začiatok. Postupne bude riadok textu v dolnej časti appletu vysvetľovať, čo sa deje počas každého kroku.
Pre tých z vás, ktorí nemôžu spustiť applet, je tu tabuľka, ktorá zobrazuje postupnosť udalostí demonštrovaných appletom:
Závit | Vyhlásenie | r | g | b | Farba |
žiadny | objekt predstavuje zelenú farbu | 0 | 255 | 0 | |
Modrá | modré vlákno vyvolá setColor (0, 0, 255) | 0 | 255 | 0 | |
Modrá | checkRGBVals (0, 0, 255); | 0 | 255 | 0 | |
Modrá | this.r = 0; | 0 | 255 | 0 | |
Modrá | this.g = 0; | 0 | 255 | 0 | |
Modrá | modrá dostane prednosť | 0 | 0 | 0 | |
červená | červené vlákno vyvoláva setColor (255, 0, 0) | 0 | 0 | 0 | |
červená | checkRGBVals (255, 0, 0); | 0 | 0 | 0 | |
červená | this.r = 255; | 0 | 0 | 0 | |
červená | this.g = 0; | 255 | 0 | 0 | |
červená | this.b = 0; | 255 | 0 | 0 | |
červená | červená niť sa vráti | 255 | 0 | 0 | |
Modrá | neskôr modrá niť pokračuje | 255 | 0 | 0 | |
Modrá | this.b = 255 | 255 | 0 | 0 | |
Modrá | modrá niť sa vráti | 255 | 0 | 255 | |
žiadny | objekt predstavuje purpurovú farbu | 255 | 0 | 255 |
Ako vidíte z tohto appletu a tabuľky, RGBColor
je poškodený, pretože plánovač vlákien preruší modré vlákno, kým je objekt stále v dočasne neplatnom stave. Keď vstúpi červená niť a zafarbí objekt na červeno, modrá niť je farbou natretá iba čiastočne. Keď sa modré vlákno vráti a dokončí prácu, neúmyselne poškodí objekt.
Konflikty čítania a zápisu
Iný druh nesprávneho správania, ktorý sa môže vyskytnúť v prostredí s viacerými vláknami RGBColor
triedy sú konflikty čítania a zápisu. Tento druh konfliktu vzniká, keď je stav objektu načítaný a používaný v dočasne neplatnom stave z dôvodu nedokončenej práce iného vlákna.
Napríklad si všimnite, že počas vykonávania modrej nite setColor ()
vyššie, objekt sa v jednom okamihu ocitne v dočasne neplatnom stave čiernej. Čierna je tu dočasne neplatný stav, pretože:
Je to dočasné: Nakoniec má modrá niť v úmysle nastaviť farbu na modrú.
- Je neplatné: Nikto nepožiadal o čiernu farbu
RGBColor
objekt. Modrá niť má zmeniť zelený predmet na modrý.
Ak je modré vlákno v danom okamihu vopred zabránené, objekt predstavuje čierne vlákno, ktoré vyvoláva getColor ()
na rovnakom objekte by toto druhé vlákno sledovalo RGBColor
hodnota objektu bude čierna.
Tu je tabuľka, ktorá zobrazuje postupnosť udalostí, ktoré by mohli viesť práve k takémuto konfliktu čítania a zápisu:
Závit | Vyhlásenie | r | g | b | Farba |
žiadny | objekt predstavuje zelenú farbu | 0 | 255 | 0 | |
Modrá | modré vlákno vyvolá setColor (0, 0, 255) | 0 | 255 | 0 | |
Modrá | checkRGBVals (0, 0, 255); | 0 | 255 | 0 | |
Modrá | this.r = 0; | 0 | 255 | 0 | |
Modrá | this.g = 0; | 0 | 255 | 0 | |
Modrá | modrá dostane prednosť | 0 | 0 | 0 | |
červená | červené vlákno vyvoláva getColor () | 0 | 0 | 0 | |
červená | int [] retVal = nový int [3]; | 0 | 0 | 0 | |
červená | retVal [0] = 0; | 0 | 0 | 0 | |
červená | retVal [1] = 0; | 0 | 0 | 0 | |
červená | retVal [2] = 0; | 0 | 0 | 0 | |
červená | návrat retVal; | 0 | 0 | 0 | |
červená | červená niť sa vracia čierna | 0 | 0 | 0 | |
Modrá | neskôr modrá niť pokračuje | 0 | 0 | 0 | |
Modrá | this.b = 255 | 0 | 0 | 0 | |
Modrá | modrá niť sa vráti | 0 | 0 | 255 | |
žiadny | objekt predstavuje modrú farbu | 0 | 0 | 255 |
Ako môžete vidieť z tejto tabuľky, problém začína, keď sa modrá niť preruší, keď objekt natrie na modro iba čiastočne. V tomto okamihu je objekt v dočasne neplatnom stave čiernej farby, čo presne vidí červená niť, keď sa vyvolá getColor ()
na objekte.
Tri spôsoby, ako zaistiť bezpečnosť objektu voči vláknam
V zásade existujú tri prístupy, ktoré môžete urobiť pri výrobe objektu, ako je napr RGBThread
bezpečné pre vlákna:
- Synchronizujte kritické sekcie
- Nech je to nemenné
- Použite obal bezpečný pre nite
Prístup 1: Synchronizácia kritických častí
Najpriamočiarejší spôsob nápravy neposlušného správania objektov, ako sú napr RGBColor
keď je umiestnený vo viacvláknovom kontexte, má sa synchronizovať kritická časť objektu. Objekt kritické úseky sú tie metódy alebo bloky kódu v rámci metód, ktoré musia byť súčasne vykonávané iba jedným vláknom. Inými slovami, kritickou časťou je metóda alebo blok kódu, ktorý sa musí vykonať atómovo ako jedna nedeliteľná operácia. Použitím Java synchronizované
kľúčové slovo, môžete zaručiť, že kritické sekcie objektu vykoná vždy iba jedno vlákno naraz.
Ak chcete použiť tento prístup k zaisteniu bezpečnosti svojho objektu s vláknami, musíte postupovať podľa dvoch krokov: musíte všetky príslušné polia označiť ako súkromné a všetky kritické sekcie musíte identifikovať a synchronizovať.
Krok 1: Pole označte ako súkromné
Synchronizácia znamená, že iba jedno vlákno naraz bude schopné vykonať kúsok kódu (kritická časť). Takže aj keď je polia ak chcete koordinovať prístup medzi viacerými vláknami, mechanizmus Java to skutočne koordinuje prístup k kód. To znamená, že iba ak údaje označíte ako súkromné, budete môcť riadiť prístup k týmto údajom riadením prístupu ku kódu, ktorý s údajmi manipuluje.