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:
- Először az alkalmazás kölcsönkér egy szálat a készletből.
- Ezután néhány szál által korlátozott értéket tárol az aktuális szálba ThreadLocal.
- 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.
- Egy idő után az alkalmazás kölcsönveszi ugyanazt a szálat egy másik kérelem feldolgozásához.
- 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.