Úvod do ThreadLocal v Jave

1. Prehľad

V tomto článku sa pozrieme na ThreadLocal konštruovať z java.lang balíček. To nám dáva možnosť ukladať údaje jednotlivo pre aktuálne vlákno - a jednoducho ich zabaliť do špeciálneho typu objektu.

2. ThreadLocal API

The TheadLocal Konštrukt nám umožňuje ukladať dáta, ktoré budú prístupné iba od konkrétne vlákno.

Povedzme, že chceme mať Celé číslo hodnota, ktorá bude spojená s konkrétnym vláknom:

ThreadLocal threadLocalValue = nový ThreadLocal ();

Ďalej, keď chceme použiť túto hodnotu z vlákna, stačí zavolať a dostať () alebo sada () metóda. Jednoducho povedané, môžeme si to myslieť ThreadLocal ukladá údaje do mapy - s vláknom ako kľúčom.

Z tohto dôvodu, keď voláme a dostať () metóda na threadLocalValue, dostaneme Celé číslo hodnota pre požadované vlákno:

threadLocalValue.set (1); Celé číslo výsledok = threadLocalValue.get ();

Môžeme zostrojiť inštanciu ThreadLocal pomocou withInitial () statická metóda a odovzdanie dodávateľovi:

ThreadLocal threadLocal = ThreadLocal.withInitial (() -> 1);

Ak chcete odstrániť hodnotu z ThreadLocal, môžeme nazvať odstrániť () metóda:

threadLocal.remove ();

Ak chcete zistiť, ako používať ThreadLocal správne, najskôr sa pozrieme na príklad, ktorý nepoužíva a ThreadLocal, potom náš príklad prepíšeme, aby sme tento konštrukt využili.

3. Ukladanie údajov používateľa na mapu

Uvažujme o programe, ktorý musí ukladať špecifické pre používateľa Kontext údaje na dané ID používateľa:

public class Context {private String userName; public Context (String userName) {this.userName = userName; }}

Chceme mať jedno vlákno na každé užívateľské ID. Vytvoríme SharedMapWithUserContext trieda, ktorá implementuje Spustiteľné rozhranie. Implementácia v run () metóda volá nejakú databázu cez UserRepository trieda, ktorá vracia a Kontext objekt pre daný ID používateľa.

Ďalej tento kontext uložíme do súboru ConcurentHashMap označené kľúčom ID používateľa:

verejná trieda SharedMapWithUserContext implementuje Runnable {public static Map userContextPerUserId = new ConcurrentHashMap (); private Integer userId; private UserRepository userRepository = new UserRepository (); @Override public void run () {String userName = userRepository.getUserNameForUserId (userId); userContextPerUserId.put (userId, nový kontext (userName)); } // štandardný konštruktor}

Náš kód môžeme ľahko otestovať vytvorením a spustením dvoch vlákien pre dve rôzne userIds a tvrdenie, že máme dva záznamy v userContextPerUserId mapa:

SharedMapWithUserContext firstUser = nový SharedMapWithUserContext (1); SharedMapWithUserContext secondUser = nový SharedMapWithUserContext (2); new Thread (firstUser) .start (); new Thread (secondUser) .start (); assertEquals (SharedMapWithUserContext.userContextPerUserId.size (), 2);

4. Ukladanie údajov používateľa do ThreadLocal

Náš príklad môžeme prepísať, aby sme používateľa uložili Kontext napríklad pomocou a ThreadLocal. Každé vlákno bude mať svoje vlastné ThreadLocal inštancia.

Pri použití ThreadLocal, musíme byť veľmi opatrní, pretože každý ThreadLocal inštancia je spojená s konkrétnym vláknom. V našom príklade máme vyhradené vlákno pre každý konkrétny prípad ID používateľa, a toto vlákno je vytvárané nami, takže nad ním máme úplnú kontrolu.

The run () metóda načíta kontext používateľa a uloží ho do súboru ThreadLocal premenná pomocou sada () metóda:

public class ThreadLocalWithUserContext implementuje Runnable {private static ThreadLocal userContext = new ThreadLocal (); private Integer userId; private UserRepository userRepository = new UserRepository (); @Override public void run () {String userName = userRepository.getUserNameForUserId (userId); userContext.set (new Context (userName)); System.out.println ("kontext vlákna pre dané userId:" + userId + "je:" + userContext.get ()); } // štandardný konštruktor}

Môžeme to otestovať spustením dvoch vlákien, ktoré vykonajú akciu pre daný ID používateľa:

ThreadLocalWithUserContext firstUser = nový ThreadLocalWithUserContext (1); ThreadLocalWithUserContext secondUser = nový ThreadLocalWithUserContext (2); new Thread (firstUser) .start (); new Thread (secondUser) .start ();

Po spustení tohto kódu uvidíme na štandardnom výstupe, že ThreadLocal bol nastavený na dané vlákno:

kontext vlákna pre daného userId: 1 je: Context {userNameSecret = '18a78f8e-24d2-4abf-91d6-79eaa198123f'} kontext vlákna pre daného userId: 2 je: Context {userNameSecret = 'e19f6a0a-253e-423e-8b2b-bca1f471}

Vidíme, že každý z používateľov má svoje vlastné Kontext.

5. ThreadLocala bazény závitov

ThreadLocal poskytuje ľahko použiteľné API na obmedzenie niektorých hodnôt na každé vlákno. Toto je rozumný spôsob dosiahnutia bezpečnosti vlákien v Jave. Avšak mali by sme byť pri používaní obzvlášť opatrní ThreadLocals a niť bazény dohromady.

Aby sme lepšie porozumeli možným výhradám, zvážme nasledujúci scenár:

  1. Najskôr si aplikácia požičia vlákno z bazéna.
  2. Potom uloží niektoré hodnoty obmedzené na vlákno do aktuálneho vlákna ThreadLocal.
  3. Po dokončení aktuálneho vykonávania aplikácia vráti vypožičané vlákno do fondu.
  4. Po chvíli si aplikácia požičia rovnaké vlákno na vybavenie ďalšej žiadosti.
  5. Pretože aplikácia naposledy nevykonala potrebné čistenie, môže znova použiť to isté ThreadLocal údaje pre novú žiadosť.

To môže spôsobiť prekvapivé následky vo veľmi súbežných aplikáciách.

Jedným zo spôsobov riešenia tohto problému je ich manuálne odstránenie ThreadLocal akonáhle to skončíme s používaním. Pretože tento prístup vyžaduje dôkladné kontroly kódu, môže byť náchylný na chyby.

5.1. Predĺženie ThreadPoolExecutor

Ukázalo sa, že, je možné predĺžiť ThreadPoolExecutor triedy a poskytnúť implementáciu vlastného háku pre beforeExecute () a afterExecute () metódy. Fond vlákien bude volať beforeExecute () pred spustením čokoľvek pomocou vypožičaného vlákna. Na druhej strane to bude volať afterExecute () metóda po vykonaní našej logiky.

Preto môžeme predĺžiť ThreadPoolExecutor triedy a odstrániť ThreadLocal údaje v afterExecute () metóda:

verejná trieda ThreadLocalAwareThreadPool rozširuje ThreadPoolExecutor {@Override chránený void afterExecute (Runnable r, Throwable t) {// Volanie remove na každom ThreadLocal}}

Ak predložíme naše žiadosti o túto implementáciu ExecutorService, potom si môžeme byť istí, že pomocou ThreadLocal a oblasti vlákien nebudú pre našu aplikáciu predstavovať bezpečnostné riziká.

6. Záver

V tomto rýchlom článku sme sa pozreli na ThreadLocal konštrukt. Implementovali sme logiku, ktorá sa používa ConcurrentHashMap ktorý bol zdieľaný medzi vláknami na uloženie kontextu spojeného s konkrétnym ID používateľa. Ďalej sme prepísali náš príklad na pákový efekt ThreadLocal na ukladanie údajov, ktoré sú spojené s konkrétnym ID používateľa a s konkrétnym vláknom.

Implementáciu všetkých týchto príkladov a útržkov kódu nájdete na GitHub.


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