Uvod u ThreadLocal u Javi

1. Pregled

U ovom ćemo članku pogledati ThreadLocal konstruirati iz java.lang paket. To nam daje mogućnost pojedinačnog pohranjivanja podataka za trenutnu nit - i jednostavno umotavanje u posebnu vrstu objekta.

2. ThreadLocal API

The TheadLocal konstrukt omogućuje nam pohranu podataka koji će biti dostupno samo po određena nit.

Recimo da želimo imati Cijeli broj vrijednost koja će biti u paketu s određenom niti:

ThreadLocal threadLocalValue = novi ThreadLocal ();

Dalje, kada želimo koristiti ovu vrijednost iz niti, trebamo nazvati samo a dobiti() ili postavi () metoda. Jednostavno rečeno, možemo to misliti ThreadLocal pohranjuje podatke unutar karte - s nitom kao ključem.

Zbog te činjenice, kada nazovemo a dobiti() metoda na threadLocalValue, dobit ćemo Cijeli broj vrijednost za nit koja traži:

threadLocalValue.set (1); Cjelobrojni rezultat = threadLocalValue.get ();

Možemo konstruirati primjerak ThreadLocal pomoću withInitial () statička metoda i prosljeđivanje dobavljača:

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

Da biste uklonili vrijednost iz ThreadLocal, možemo nazvati ukloniti() metoda:

threadLocal.remove ();

Da biste vidjeli kako koristiti ThreadLocal pravilno, prvo, pogledat ćemo primjer koji ne koristi a ThreadLocal, tada ćemo prepisati naš primjer kako bismo iskoristili taj konstrukt.

3. Pohranjivanje korisničkih podataka na karti

Razmotrimo program koji treba pohraniti specifičnog korisnika Kontekst podaci po zadanom korisničkom ID-u:

javna klasa Kontekst {private String userName; javni kontekst (niz userName) {this.userName = userName; }}

Želimo imati jednu nit po korisničkom ID-u. Stvorit ćemo a SharedMapWithUserContext klasa koja provodi Izvodljivo sučelje. Provedba u trčanje() metoda poziva neku bazu podataka putem UserRepository klasa koja vraća a Kontekst objekt za dano userId.

Dalje, taj kontekst pohranjujemo u ConcurentHashMap ključ od userId:

javna klasa SharedMapWithUserContext implementira Runnable {javna statička karta userContextPerUserId = new ConcurrentHashMap (); private Integer userId; private UserRepository userRepository = novi UserRepository (); @Override public void run () {String userName = userRepository.getUserNameForUserId (userId); userContextPerUserId.put (userId, novi kontekst (userName)); } // standardni konstruktor}

Naš kôd možemo lako testirati stvaranjem i pokretanjem dviju niti za dvije različite userIds i tvrdeći da imamo dva unosa u userContextPerUserId karta:

SharedMapWithUserContext firstUser = novo SharedMapWithUserContext (1); SharedMapWithUserContext secondUser = novi SharedMapWithUserContext (2); nova nit (firstUser) .start (); nova nit (secondUser) .start (); assertEquals (SharedMapWithUserContext.userContextPerUserId.size (), 2);

4. Pohranjivanje korisničkih podataka u ThreadLocal

Naš primjer možemo prepisati kako bismo spremili korisnika Kontekst instanci pomoću a ThreadLocal. Svaka nit će imati svoju ThreadLocal primjer.

Prilikom korištenja ThreadLocal, moramo biti vrlo oprezni jer svaki ThreadLocal instanca je povezana s određenom niti. U našem primjeru imamo posebnu nit za svaku pojedinu userId, a ovu nit stvaramo mi, tako da imamo potpunu kontrolu nad njom.

The trčanje() metoda će dohvatiti korisnički kontekst i spremiti ga u ThreadLocal varijabla pomoću postavi () metoda:

javna klasa ThreadLocalWithUserContext implementira Runnable {private static ThreadLocal userContext = new ThreadLocal (); private Integer userId; private UserRepository userRepository = novi UserRepository (); @Override public void run () {String userName = userRepository.getUserNameForUserId (userId); userContext.set (novi kontekst (userName)); System.out.println ("Kontekst niti za zadani userId:" + userId + "je:" + userContext.get ()); } // standardni konstruktor}

Možemo ga testirati pokretanjem dviju niti koje će izvršiti radnju za zadani userId:

ThreadLocalWithUserContext firstUser = novi ThreadLocalWithUserContext (1); ThreadLocalWithUserContext secondUser = novi ThreadLocalWithUserContext (2); nova nit (firstUser) .start (); nova nit (secondUser) .start ();

Nakon pokretanja ovog koda vidjet ćemo na standardnom izlazu da ThreadLocal postavljeno je po zadanoj niti:

Kontekst niti za zadani userId: 1 je: Kontekst {userNameSecret = '18a78f8e-24d2-4abf-91d6-79eaa198123f'} Kontekst niti za zadani userId: 2 je: Kontekst {userNameSecret = 'e19f6a0a-253e-423e -8c2bea-8c2b-ec

Vidimo da svaki od korisnika ima svoj Kontekst.

5. ThreadLocals i Thread Pools

ThreadLocal pruža jednostavan API za ograničavanje nekih vrijednosti na svaku nit. Ovo je razuman način za postizanje sigurnosti niti u Javi. Međutim, trebali bismo biti posebno oprezni kada koristimo ThreadLocals i bazeni niti zajedno.

Da bismo bolje razumjeli moguće upozorenje, razmotrimo sljedeći scenarij:

  1. Prvo, aplikacija posuđuje nit iz bazena.
  2. Zatim sprema neke vrijednosti ograničene nitima u trenutnu nit ThreadLocal.
  3. Kada se trenutno izvršavanje završi, aplikacija vraća posuđenu nit u spremište.
  4. Nakon nekog vremena, aplikacija posuđuje istu nit za obradu drugog zahtjeva.
  5. Budući da aplikacija prošli put nije izvršila potrebna čišćenja, može je ponovno upotrijebiti ThreadLocal podaci za novi zahtjev.

To može izazvati iznenađujuće posljedice u istodobnim aplikacijama.

Jedan od načina rješavanja ovog problema je ručno uklanjanje svakog ThreadLocal nakon što završimo s korištenjem. Budući da ovaj pristup treba stroge preglede koda, on može biti sklon pogreškama.

5.1. Proširivanje ThreadPoolExeecuter

Kako se ispostavilo, moguće je produžiti ThreadPoolExeecuter klase i pružiti prilagođenu implementaciju kuke za beforeExecute () i afterExecute () metode. Spremište niti pozvat će beforeExecute () metoda prije pokretanja bilo čega koristeći posuđenu nit. S druge strane, nazvat će afterExecute () metoda nakon izvršavanja naše logike.

Stoga možemo proširiti ThreadPoolExeecuter razred i uklonite ThreadLocal podaci u afterExecute () metoda:

javna klasa ThreadLocalAwareThreadPool proširuje ThreadPoolExecutor {@Override protected void afterExecute (Runnable r, Throwable t) {// poziv uklonite na svakom ThreadLocal}}

Ako svoje zahtjeve podnesemo ovoj provedbi ExecutorService, tada možemo biti sigurni da pomoću ThreadLocal a bazeni niti neće predstavljati sigurnosne opasnosti za našu primjenu.

6. Zaključak

U ovom kratkom članku gledali smo ThreadLocal konstruirati. Proveli smo logiku koja koristi ConcurrentHashMap koje su dijelile niti za pohranu konteksta povezanog s određenim userId. Zatim smo svoj primjer prepisali kako bismo iskoristili ThreadLocal za pohranu podataka koji su povezani s određenim userId i s određenom niti.

Provedbu svih ovih primjera i isječaka koda možete pronaći na GitHubu.