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ů:
Podpora více vláken je zabudována do jazyka Java a API
- 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, int
s 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 RGBColor
konstruktor a metody. Návrh konstruktoru a metod zaručuje, že:
RGBColor
Konstruktor vždy dá proměnným správné počáteční hodnotyMetody
setColor ()
ainvertovat ()
na těchto proměnných vždy provede platné transformace stavu- 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.
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ákno | Prohlášení | r | G | b | Barva |
žádný | objekt představuje zelenou | 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 přednost | 0 | 0 | 0 | |
Červené | červená nit vyvolá 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á nit se vrací | 255 | 0 | 0 | |
modrý | později pokračuje modrá nit | 255 | 0 | 0 | |
modrý | this.b = 255 | 255 | 0 | 0 | |
modrý | modrá nit se vrací | 255 | 0 | 255 | |
žádný | objekt představuje purpurovou | 255 | 0 | 255 |
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:
Je to dočasné: Nakonec má modrá nit v úmyslu nastavit barvu na modrou.
- 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ákno | Prohlášení | r | G | b | Barva |
žádný | objekt představuje zelenou | 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 přednost | 0 | 0 | 0 | |
Červené | červená nit vyvolá 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ávratový retVal; | 0 | 0 | 0 | |
Červené | červená nit vrací černou | 0 | 0 | 0 | |
modrý | později pokračuje modrá nit | 0 | 0 | 0 | |
modrý | this.b = 255 | 0 | 0 | 0 | |
modrý | modrá nit se vrací | 0 | 0 | 255 | |
žádný | objekt představuje modrou | 0 | 0 | 255 |
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:
- Synchronizujte kritické sekce
- Udělejte to neměnným
- 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.