Prečo nezačať vlákno v konštruktore?

1. Prehľad

V tomto rýchlom návode sa dozvieme, prečo by sme nemali začínať vlákno vo vnútri konštruktora.

Najskôr v krátkosti predstavíme koncept publikácie v jazykoch Java a JVM. Potom uvidíme, ako tento koncept ovplyvní spôsob, akým začíname vlákna.

2. Publikácia a únik

Zakaždým, keď sprístupníme objekt akémukoľvek inému kódu mimo jeho súčasného rozsahu, v zásade ho zverejníme. Napríklad k publikovaniu dôjde, keď vrátime objekt a uložíme ho do a verejné odkaz, alebo ho dokonca odovzdať inej metóde.

Keď zverejníme objekt, ktorý by sme nemali mať, hovoríme, že objekt unikol.

Existuje mnoho spôsobov, ako môžeme nechať referenciu na objekt uniknúť, napríklad zverejnenie objektu pred jeho úplnou konštrukciou. V skutočnosti ide o jednu z bežných foriem úniku: keď toto referenčné úniky počas stavby objektu.

Keď toto referencia unikne počas výstavby, iné vlákna môžu vidieť tento objekt v nesprávnom a nie úplne zostavenom stave. To zase môže spôsobiť podivné bezpečnostné komplikácie vlákna.

3. Únik pomocou závitov

Jeden z najbežnejších spôsobov prenájmu toto referenčný únik je spustenie vlákna v konštruktore. Aby sme tomu lepšie porozumeli, zvážme príklad:

verejná trieda LoggerRunnable implementuje Runnable {public LoggerRunnable () {vlákno vlákna = nové vlákno (toto); // týmto unikne thread.start (); } @Override public void run () {System.out.println ("Spustené ..."); }}

Tu výslovne míňame znak toto odkaz na Závit konštruktér. Preto novo spustené vlákno môže vidieť obklopujúci objekt skôr, ako je dokončená jeho úplná konštrukcia. V súbežných kontextoch to môže spôsobiť jemné chyby.

Je tiež možné prejsť toto odkaz implicitne:

public class ImplicitEscape {public ImplicitEscape () {Thread t = new Thread () {@Override public void run () {System.out.println ("Started ..."); }}; t.start (); }}

Ako je uvedené vyššie, vytvárame anonymnú vnútornú triedu odvodenú z Závit. Pretože vnútorné triedy udržiavajú odkaz na svoju uzatvárajúcu triedu, znak toto referencia opäť unikne z konštruktora.

Na vytvorení a nie je nič inherentne zlé Závit vo vnútri konštruktora. Je však veľmi odradené okamžite ho začať, pretože väčšinou skončíme útekom toto výslovne alebo implicitne.

3.1. Alternatívy

Namiesto spustenia vlákna vo vnútri konštruktora môžeme deklarovať vyhradenú metódu pre tento scenár:

verejná trieda SafePublication implementuje Runnable {private final Thread thread; public SafePublication () {thread = new Thread (this); } @Override public void run () {System.out.println ("Spustené ..."); } public void start () {thread.start (); }} ;:

Ako je uvedené vyššie, stále zverejňujeme toto odkaz na Závit. Tentokrát však začneme vlákno po návrate konštruktora:

Publikácia SafePublication = nová SafePublication (); publikácia.štart ();

Odkaz na objekt preto pred úplnou konštrukciou neunikne do iného vlákna.

4. Záver

V tomto rýchlom návode sme po krátkom úvode do bezpečnej publikácie zistili, prečo by sme nemali začínať vlákno vo vnútri konštruktora.

Podrobnejšie informácie o publikácii a úniku v prostredí Java nájdete v knihe Java Concurrency in Practice.

Ako obvykle sú všetky príklady dostupné na GitHub.