Programování

Design pro bezpečnost závitů

Před šesti měsíci jsem začal sérii článků o navrhování tříd a objektů. V tomto měsíci Techniky návrhu sloupci, budu pokračovat v této sérii tím, že se podívám na principy návrhu, které se týkají bezpečnosti vláken. Tento článek vám řekne, co je bezpečnost nití, proč ji potřebujete, kdy ji potřebujete a jak ji získat.

Co je to bezpečnost nití?

Zabezpečení vláken jednoduše znamená, že pole objektu nebo třídy vždy udržují platný stav, jak je pozorováno jinými objekty a třídami, i když jsou používána souběžně více vlákny.

Jedním z prvních pokynů, které jsem v tomto sloupci navrhl (viz „Návrh inicializace objektu“), je, že byste měli navrhovat třídy tak, aby si objekty udržovaly platný stav od začátku jejich životnosti až do konce. Pokud budete postupovat podle této rady a vytvoříte objekty, jejichž proměnné instance jsou všechny soukromé a jejichž metody provádějí pouze správné přechody stavu na těchto proměnných instance, jste v prostředí s jedním vláknem v dobrém stavu. Ale když přijde více vláken, můžete se dostat do potíží.

Více vláken může způsobit problém vašeho objektu, protože často, když je metoda v procesu provádění, může být stav vašeho objektu dočasně neplatný. Když pouze jedno vlákno vyvolává metody objektu, bude vždy prováděna pouze jedna metoda najednou a každé metodě bude povoleno dokončit před vyvoláním jiné metody. V prostředí s jedním podprocesem tedy bude mít každá metoda šanci zajistit, aby se jakýkoli dočasně neplatný stav změnil na platný před tím, než se metoda vrátí.

Jakmile však zavedete více vláken, může JVM přerušit vlákno provádějící jednu metodu, zatímco proměnné instance objektu jsou stále v dočasně neplatném stavu. JVM by pak mohl dát šanci provést jiné vlákno a toto vlákno by mohlo volat metodu na stejný objekt. Veškerá vaše tvrdá práce, aby vaše proměnné instance byly soukromé, a vaše metody provádějí pouze platné transformace stavu, nebudou stačit, aby zabránily tomuto druhému vláknu v pozorování objektu v neplatném stavu.

Takový objekt by nebyl bezpečný pro vlákna, protože ve vícevláknovém prostředí by se objekt mohl poškodit nebo by bylo možné pozorovat, že má neplatný stav. Objekt bezpečný pro vlákna je ten, který vždy udržuje platný stav, jak je pozorováno jinými třídami a objekty, a to i v prostředí s více vlákny.

Proč se starat o bezpečnost nití?

Při navrhování tříd a objektů v Javě musíte myslet na bezpečnost podprocesů ze dvou velkých důvodů:

  1. Podpora více vláken je zabudována do jazyka Java a API

  2. Všechna vlákna uvnitř virtuálního počítače Java (JVM) sdílejí stejnou haldu a oblast metody

Protože multithreading je zabudován do Javy, je možné, že kteroukoli třídu, kterou nakonec navrhnete, může být současně použita více vlákny. Nemusíte (a neměli byste) dělat každou třídu, kterou navrhujete, bezpečnou pro vlákna, protože bezpečnost vláken není zadarmo. Ale měli byste alespoň myslet si o bezpečnosti vláken při každém návrhu třídy Java. Níže v tomto článku najdete diskusi o nákladech na bezpečnost podprocesů a pokyny týkající se toho, kdy provést třídy bezpečné pro podprocesy.

Vzhledem k architektuře JVM se musíte starat pouze o proměnné instance a třídy, když se obáváte o bezpečnost podprocesů. Protože všechna vlákna sdílejí stejnou haldu a v haldě jsou uloženy všechny proměnné instance, může se více vláken pokusit použít proměnné instance stejného objektu současně. Podobně protože všechna vlákna sdílejí stejnou oblast metody a oblast metody je místo, kde jsou uloženy všechny proměnné třídy, může se více vláken pokusit použít stejné proměnné třídy současně. Když se rozhodnete vytvořit třídu bezpečnou pro vlákna, vaším cílem je zaručit integritu - v prostředí s více vlákny - proměnných instance a třídy deklarovaných v této třídě.

Nemusíte si dělat starosti s vícevláknovým přístupem k místním proměnným, parametrům metod a návratovým hodnotám, protože tyto proměnné jsou umístěny v zásobníku Java. V JVM má každé vlákno svůj vlastní zásobník Java. Žádné vlákno nemůže vidět ani používat žádné místní proměnné, návratové hodnoty nebo parametry patřící do jiného vlákna.

Vzhledem ke struktuře JVM jsou místní proměnné, parametry metody a návratové hodnoty ze své podstaty „bezpečné pro vlákna“. Proměnné instance a proměnné třídy však budou bezpečné pouze pro vlákna, pokud vhodně navrhnete svou třídu.

RGBColor # 1: Připraveno pro jedno vlákno

Jako příklad třídy, která je ne bezpečné pro vlákna, zvažte RGBColor třída, viz níže. Instance této třídy představují barvu uloženou ve třech proměnných soukromé instance: r, G, a b. Vzhledem k níže uvedené třídě, an RGBColor objekt by zahájil svůj život v platném stavu a zažil by pouze přechody platného stavu, od začátku svého života do konce - ale pouze v prostředí s jedním vláknem.

// V souborových vláknech / ex1 / RGBColor.java // Instance této třídy NEJSOU bezpečné pro vlákna. public class RGBColor {private int r; soukromé int g; soukromé 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; } / ** * vrací barvu v poli tří ints: R, G a B * / public int [] getColor () {int [] retVal = new int [3]; retVal [0] = r; retVal [1] = g; retVal [2] = b; návratový 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 (); }}} 

Protože tři proměnné instance, ints r, G, a b, jsou soukromé, jediný způsob, jakým mohou ostatní třídy a objekty přistupovat nebo ovlivňovat hodnoty těchto proměnných, je přes RGBColorkonstruktor a metody. Návrh konstruktoru a metod zaručuje, že:

  1. RGBColorKonstruktor vždy dá proměnným správné počáteční hodnoty

  2. Metody setColor () a invertovat () na těchto proměnných vždy provede platné transformace stavu

  3. Metoda getColor () vždy vrátí platné zobrazení těchto proměnných

Všimněte si, že pokud jsou špatná data předána konstruktoru nebo setColor () metodou, najednou dokončí s InvalidArgumentException. The checkRGBVals () metoda, která vyvolá tuto výjimku, ve skutečnosti definuje, co to znamená pro RGBColor objekt k platnosti: hodnoty všech tří proměnných, r, G, a b, musí být mezi 0 a 255 včetně. Kromě toho, aby byla platná, musí být barva představovaná těmito proměnnými nejnovější barvou předanou konstruktoru nebo setColor () metodou nebo vyrobenou invertovat () metoda.

Pokud v prostředí s jedním vláknem vyvoláte setColor () a projít modře, RGBColor objekt bude modrý, když setColor () se vrací. Pokud pak vyvoláte getColor () na stejném objektu získáte modrou barvu. Ve společnosti s jedním vláknem jsou to příklady RGBColor třídy jsou dobře vychovaní.

Vržení souběžného klíče do díla

Bohužel tento šťastný obrázek dobře vychovaného RGBColor objekt může být děsivý, když do obrazu vstoupí další vlákna. V prostředí s více podprocesy jsou instance RGBColor třída definovaná výše je náchylná ke dvěma druhům špatného chování: konflikty zápisu / zápisu a konflikty čtení / zápisu.

Konflikty zápisu / zápisu

Představte si, že máte dvě vlákna, jedno vlákno s názvem „červené“ a druhé s názvem „modré“. Obě vlákna se snaží nastavit stejnou barvu RGBColor objekt: Červená nit se pokouší nastavit barvu na červenou; modrá nit se pokouší nastavit barvu na modrou.

Obě tato vlákna se pokoušejí současně zapisovat do proměnných instance stejného objektu. Pokud plánovač podprocesů prokládá tyto dvě podprocesy správným způsobem, budou se obě podprocesy navzájem neúmyslně rušit a způsobí konflikt zápisu / zápisu. V tomto procesu dvě vlákna poškodí stav objektu.

The Nesynchronizované RGBColor applet

Následující applet s názvem Nesynchronizovaná RGBColor, ukazuje jednu sekvenci událostí, které by mohly vést k poškození RGBColor objekt. Červená nit se nevinně snaží nastavit barvu na červenou, zatímco modrá nit se nevinně snaží nastavit barvu na modrou. Nakonec RGBColor objekt nepředstavuje ani červenou ani modrou barvu, ale znepokojivou barvu, purpurovou.

Z nějakého důvodu vám váš prohlížeč nedovolí vidět tento skvělý applet Java.

Projít sledem událostí, které vedou k poškození RGBColor objektu, stiskněte tlačítko Krok appletu. Stisknutím Zpět provedete zálohování kroku a stisknutím Obnovit provedete zálohování na začátek. Postupně bude řádek textu ve spodní části appletu vysvětlovat, co se děje během každého kroku.

Pro ty z vás, kteří applet nemohou spustit, je zde tabulka, která ukazuje sled událostí demonstrovaných appletem:

VláknoProhlášenírGbBarva
žádnýobjekt představuje zelenou02550 
modrýmodré vlákno vyvolá setColor (0, 0, 255)02550 
modrýcheckRGBVals (0, 0, 255);02550 
modrýthis.r = 0;02550 
modrýthis.g = 0;02550 
modrýmodrá dostane přednost000 
Červenéčervená nit vyvolá setColor (255, 0, 0)000 
ČervenécheckRGBVals (255, 0, 0);000 
Červenéthis.r = 255;000 
Červenéthis.g = 0;25500 
Červenéthis.b = 0;25500 
Červenéčervená nit se vrací25500 
modrýpozději pokračuje modrá nit25500 
modrýthis.b = 25525500 
modrýmodrá nit se vrací2550255 
žádnýobjekt představuje purpurovou2550255 

Jak vidíte z tohoto appletu a tabulky, RGBColor je poškozen, protože plánovač podprocesů přerušuje modré vlákno, zatímco objekt je stále v dočasně neplatném stavu. Když přijde červená nit a namaluje objekt červenou barvou, je modrá nit dokončena pouze částečně a namaluje objekt modře. Když se modré vlákno vrátí a dokončí úlohu, neúmyslně poškodí objekt.

Konflikty čtení / zápisu

Další druh nevhodného chování, který se může projevit v prostředí s více vlákny RGBColor třída je konflikty čtení / zápisu. Tento druh konfliktu nastává, když je stav objektu přečten a používán v dočasně neplatném stavu kvůli nedokončené práci jiného vlákna.

Například si všimněte, že během provádění modrého vlákna setColor () výše uvedená metoda se objekt v jednom bodě ocitne v dočasně neplatném stavu černé. Zde je černá dočasně neplatný stav, protože:

  1. Je to dočasné: Nakonec má modrá nit v úmyslu nastavit barvu na modrou.

  2. Je neplatné: Nikdo nepožádal o černou RGBColor objekt. Modrá nit má změnit zelený objekt na modrý.

Pokud je modré vlákno preempted v okamžiku, kdy objekt představuje černé vlákno, které vyvolá getColor () na stejném objektu by toto druhé vlákno pozorovalo RGBColor hodnota objektu bude černá.

Tady je tabulka, která ukazuje sled událostí, které by mohly vést právě k takovému konfliktu čtení / zápisu:

VláknoProhlášenírGbBarva
žádnýobjekt představuje zelenou02550 
modrýmodré vlákno vyvolá setColor (0, 0, 255)02550 
modrýcheckRGBVals (0, 0, 255);02550 
modrýthis.r = 0;02550 
modrýthis.g = 0;02550 
modrýmodrá dostane přednost000 
Červenéčervená nit vyvolá getColor ()000 
Červenéint [] retVal = nový int [3];000 
ČervenéretVal [0] = 0;000 
ČervenéretVal [1] = 0;000 
ČervenéretVal [2] = 0;000 
Červenénávratový retVal;000 
Červenéčervená nit vrací černou000 
modrýpozději pokračuje modrá nit000 
modrýthis.b = 255000 
modrýmodrá nit se vrací00255 
žádnýobjekt představuje modrou00255 

Jak můžete vidět z této tabulky, potíže začínají, když je modrá nit přerušena, když má objekt modře zbarvený pouze částečně. V tomto okamžiku je objekt v dočasně neplatném stavu černé, což je přesně to, co vidí červená nit, když vyvolá getColor () na objektu.

Tři způsoby, jak zajistit, aby byl objekt bezpečný pro vlákna

V zásadě existují tři přístupy, které můžete použít k vytvoření objektu, jako je RGBThread bezpečné pro vlákna:

  1. Synchronizujte kritické sekce
  2. Udělejte to neměnným
  3. Použijte obal vhodný pro vlákna

Přístup 1: Synchronizace kritických sekcí

Nejpřímější způsob, jak napravit neposlušné chování vystavené objekty, jako je RGBColor při umístění do vícevláknového kontextu je synchronizace kritických částí objektu. Objekt kritické sekce jsou ty metody nebo bloky kódu v rámci metod, které musí být provedeny pouze jedním vláknem najednou. Jinými slovy, kritická část je metoda nebo blok kódu, který musí být proveden atomicky, jako jediná nedělitelná operace. Pomocí Java synchronizované klíčové slovo, můžete zaručit, že pouze jedno vlákno najednou provede kritické části objektu.

Chcete-li využít tento přístup k tomu, aby byl váš objekt bezpečný pro vlákna, musíte postupovat podle dvou kroků: musíte všechna relevantní pole nastavit jako soukromá a musíte identifikovat a synchronizovat všechny kritické sekce.

Krok 1: Nastavit pole jako soukromá

Synchronizace znamená, že bitový kód bude možné spustit pouze v jednom vlákně najednou (kritická část). Takže i když je pole chcete koordinovat přístup mezi více vlákny, mechanismus Java, který to dělá, ve skutečnosti koordinuje přístup kód. To znamená, že pouze pokud nastavíte data jako soukromá, budete moci řídit přístup k těmto datům řízením přístupu k kódu, který s daty manipuluje.