Bevezetés a Java ThreadLocal-ba

1. Áttekintés

Ebben a cikkben megnézzük a ThreadLocal konstruálni a java.lang csomag. Ez lehetővé teszi számunkra, hogy az aktuális szálhoz külön-külön tároljuk az adatokat - és egyszerűen beburkoljuk egy speciális típusú objektumba.

2. ThreadLocal API

A TheadLocal A konstrukció lehetővé teszi számunkra az adatok tárolását csak hozzáférhető által egy adott szál.

Tegyük fel, hogy szeretnénk egy Egész szám az adott szálhoz mellékelt érték:

ThreadLocal threadLocalValue = new ThreadLocal ();

Ezután, amikor ezt az értéket egy szálból akarjuk használni, akkor csak a-t kell meghívnunk kap() vagy készlet() módszer. Egyszerűen fogalmazva, ezt gondolhatjuk ThreadLocal adatokat tárol a térképen - a szál a kulcs.

Ennek a ténynek köszönhetően, amikor a kap() módszer a threadLocalValue, kapunk egy Egész szám a kérelmező szál értéke:

threadLocalValue.set (1); Egész eredmény = threadLocalValue.get ();

Felépíthetjük a ThreadLocal a withInitial () statikus módszer és szállító átadása neki:

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

Az érték eltávolításához a ThreadLocal, hívhatjuk a eltávolítás () módszer:

threadLocal.remove ();

Hogy lássa, hogyan kell használni a ThreadLocal megfelelően, először is, megnézünk egy példát, amely nem használja a ThreadLocal, akkor átírjuk a példánkat, hogy kihasználjuk ezt a konstrukciót.

3. Felhasználói adatok tárolása térképen

Vegyünk egy programot, amelynek a felhasználóspecifikus adatokat kell tárolnia Kontextus adatok adott felhasználói azonosítónként:

public class Context {private String felhasználónév; public Context (String felhasználónév) {this.userName = felhasználónév; }}

Szeretnénk, hogy felhasználói azonosítónként egy szál legyen. Létrehozunk egy SharedMapWithUserContext osztály, amely végrehajtja a Futható felület. A végrehajtás a fuss() metódus meghív valamilyen adatbázist a UserRepository osztály, amely visszaadja a Kontextus objektum egy adott számára Felhasználói azonosító.

Ezután ezt a kontextust tároljuk a ConcurentHashMap által kulcsolt Felhasználói azonosító:

public class SharedMapWithUserContext megvalósítja a Runnable {public static Map userContextPerUserId = new ConcurrentHashMap (); private Integer userId; private UserRepository userRepository = new UserRepository (); @Orride public void run () {String felhasználónév = userRepository.getUserNameForUserId (userId); userContextPerUserId.put (userId, új kontextus (felhasználónév)); } // szabványos kivitelező}

Könnyen tesztelhetjük kódunkat úgy, hogy két különböző szálat hozunk létre és indítunk el userIds és azt állítva, hogy két bejegyzésünk van a userContextPerUserId térkép:

SharedMapWithUserContext firstUser = új SharedMapWithUserContext (1); SharedMapWithUserContext secondUser = új SharedMapWithUserContext (2); új szál (firstUser) .start (); új szál (secondUser) .start (); assertEquals (SharedMapWithUserContext.userContextPerUserId.size (), 2);

4. Felhasználói adatok tárolása ThreadLocal

Átírhatjuk a példánkat, hogy tároljuk a felhasználót Kontextus Például a ThreadLocal. Minden szálnak meg lesz a maga ThreadLocal példa.

Használat során ThreadLocal, nagyon óvatosnak kell lennünk, mert minden ThreadLocal a példány egy adott szálhoz van társítva. Példánkban külön-külön szálat mutatunk be minden egyes számára Felhasználói azonosító, és ezt a szálat mi hoztuk létre, így teljes kontrollunk van felettük.

A fuss() metódus lekéri a felhasználói kontextust, és eltárolja a ThreadLocal változó a készlet() módszer:

public class ThreadLocalWithUserContext megvalósítja a Runnable {private static ThreadLocal userContext = new ThreadLocal (); private Integer userId; private UserRepository userRepository = new UserRepository (); @Orride public void run () {String felhasználónév = userRepository.getUserNameForUserId (userId); userContext.set (új kontextus (felhasználónév)); System.out.println ("az adott userId:" + userId + "szál kontextusa:" + userContext.get ()); } // szabványos kivitelező}

Két szál elindításával tesztelhetjük, amelyek végrehajtják az adott műveletet Felhasználói azonosító:

ThreadLocalWithUserContext firstUser = új ThreadLocalWithUserContext (1); ThreadLocalWithUserContext secondUser = új ThreadLocalWithUserContext (2); új szál (firstUser) .start (); új szál (secondUser) .start ();

A kód futtatása után látni fogjuk a szabványos kimeneten ThreadLocal adott szálra lett beállítva:

az adott userId: 1 szálkontextus: Context {userNameSecret = '18a78f8e-24d2-4abf-91d6-79eaa198123f'} szál kontextus az adott userId: 2 számára: Context {userNameSecret = 'e19f6a0a-253e-423e-8b2b-bca1f47

Láthatjuk, hogy a felhasználók mindegyikének megvan a maga Kontextus.

5. ThreadLocals és szálmedencék

ThreadLocal egy könnyen használható API-t biztosít, hogy egyes értékeket egyes szálakra korlátozzon. Ez egy ésszerű módszer a szálbiztonság elérésére a Java-ban. Azonban, különösen óvatosnak kell lennünk a használat során ThreadLocals a szálmedencék együtt.

A lehetséges figyelmeztetés jobb megértése érdekében vegyük figyelembe a következő forgatókönyvet:

  1. Először az alkalmazás kölcsönkér egy szálat a készletből.
  2. Ezután néhány szál által korlátozott értéket tárol az aktuális szálba ThreadLocal.
  3. Miután az aktuális végrehajtás befejeződött, az alkalmazás visszaküldi a kölcsönzött szálat a készletbe.
  4. Egy idő után az alkalmazás kölcsönveszi ugyanazt a szálat egy másik kérelem feldolgozásához.
  5. Mivel az alkalmazás a múltkor nem hajtotta végre a szükséges tisztításokat, előfordulhat, hogy újra felhasználja ThreadLocal adatok az új kérelemhez.

Ez meglepő következményeket okozhat a nagyon egyidejű alkalmazásokban.

A probléma megoldásának egyik módja az egyes manuális eltávolítása ThreadLocal ha befejeztük a használatát. Mivel ez a megközelítés szigorú kódellenőrzéseket igényel, hibára hajlamos lehet.

5.1. A. Kiterjesztése ThreadPoolExecutor

Ahogy kiderül, lehetséges meghosszabbítani a ThreadPoolExecutor osztályban, és biztosítson egyedi horog megvalósítást a beforeExecute () és afterExecute () mód. A szálkészlet a beforeExecute () metódus, mielőtt bármit futtatna a kölcsönzött szál segítségével. Másrészt a afterExecute () módszer a logikánk végrehajtása után.

Ezért meghosszabbíthatjuk a ThreadPoolExecutor osztály és távolítsa el a ThreadLocal adatok a afterExecute () módszer:

public class ThreadLocalAwareThreadPool kiterjeszti a ThreadPoolExecutor {@Override védett void afterExecute (Futható r, Throwable t) {// Híváseltávolítás minden ThreadLocal oldalon}}

Ha benyújtjuk kéréseinket a ExecutorService, akkor biztosak lehetünk abban, hogy a ThreadLocal és a menetes medencék nem jelentenek biztonsági veszélyeket alkalmazásunk számára.

6. Következtetés

Ebben a gyors cikkben a ThreadLocal konstrukció. Megvalósítottuk a használt logikát ConcurrentHashMap amelyet megosztottak a szálak között az adott elemhez társított kontextus tárolására Felhasználói azonosító. Ezután átírtuk a példát a tőkeáttételre ThreadLocal egy adott adathoz társított adatok tárolására Felhasználói azonosító és egy adott cérnával.

Ezen példák és kódrészletek megvalósítása megtalálható a GitHub-on.