En introduksjon til ThreadLocal i Java

1. Oversikt

I denne artikkelen vil vi se på Trådlokal konstruere fra java.lang pakke. Dette gir oss muligheten til å lagre data individuelt for den gjeldende tråden - og ganske enkelt pakke den inn i en spesiell type objekt.

2. Trådlokal API

De TheadLocal konstruksjon lar oss lagre data som vil være kun tilgjengelig av en bestemt tråd.

La oss si at vi vil ha en Heltall verdi som blir samlet med den spesifikke tråden:

ThreadLocal threadLocalValue = ny ThreadLocal ();

Deretter, når vi vil bruke denne verdien fra en tråd, trenger vi bare å kalle a få() eller sett() metode. Enkelt sagt, vi kan tro det Trådlokal lagrer data på et kart - med tråden som nøkkel.

På grunn av det faktum, når vi kaller en få() metoden på threadLocalValue, vi får en Heltall verdi for forespørselstråden:

threadLocalValue.set (1); Heltalsresultat = threadLocalValue.get ();

Vi kan konstruere en forekomst av Trådlokal ved å bruke withInitial () statisk metode og sende en leverandør til den:

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

For å fjerne verdien fra Trådlokal, vi kan kalle fjerne() metode:

threadLocal.remove ();

For å se hvordan du bruker Trådlokal riktig, for det første vil vi se på et eksempel som ikke bruker a Trådlokal, så vil vi skrive om vårt eksempel for å utnytte den konstruksjonen.

3. Lagring av brukerdata på et kart

La oss vurdere et program som trenger å lagre den brukerspesifikke Kontekst data per gitt bruker-ID:

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

Vi ønsker å ha en tråd per bruker-ID. Vi lager en SharedMapWithUserContext klasse som implementerer Kjørbar grensesnitt. Gjennomføringen i løpe() metoden kaller noen database gjennom UserRepository klasse som returnerer a Kontekst objekt for en gitt bruker-ID.

Deretter lagrer vi den konteksten i ConcurentHashMap tastet av bruker-ID:

offentlig klasse SharedMapWithUserContext implementerer Runnable {public static Map userContextPerUserId = new ConcurrentHashMap (); private Integer userId; privat UserRepository userRepository = nytt UserRepository (); @ Override public void run () {String userName = userRepository.getUserNameForUserId (userId); userContextPerUserId.put (userId, ny kontekst (brukernavn)); } // standard konstruktør}

Vi kan enkelt teste koden vår ved å opprette og starte to tråder for to forskjellige userIds og hevder at vi har to oppføringer i userContextPerUserId kart:

SharedMapWithUserContext firstUser = ny SharedMapWithUserContext (1); SharedMapWithUserContext secondUser = ny SharedMapWithUserContext (2); ny tråd (firstUser) .start (); ny tråd (secondUser) .start (); assertEquals (SharedMapWithUserContext.userContextPerUserId.size (), 2);

4. Lagring av brukerdata i Trådlokal

Vi kan omskrive eksemplet vårt for å lagre brukeren Kontekst forekomst ved hjelp av en Trådlokal. Hver tråd vil ha sin egen Trådlokal forekomst.

Når du bruker Trådlokal, vi må være veldig forsiktige fordi hver Trådlokal forekomst er assosiert med en bestemt tråd. I vårt eksempel har vi en dedikert tråd for hver enkelt bruker-ID, og denne tråden er opprettet av oss, så vi har full kontroll over den.

De løpe() metoden vil hente brukerkonteksten og lagre den i Trådlokal variabel ved hjelp av sett() metode:

offentlig klasse ThreadLocalWithUserContext implementerer Runnable {private static ThreadLocal userContext = new ThreadLocal (); private Integer userId; privat UserRepository userRepository = nytt UserRepository (); @ Override public void run () {String userName = userRepository.getUserNameForUserId (userId); userContext.set (ny kontekst (brukernavn)); System.out.println ("trådkontekst for gitt userId:" + userId + "er:" + userContext.get ()); } // standard konstruktør}

Vi kan teste det ved å starte to tråder som vil utføre handlingen for en gitt bruker-ID:

ThreadLocalWithUserContext firstUser = ny ThreadLocalWithUserContext (1); ThreadLocalWithUserContext secondUser = ny ThreadLocalWithUserContext (2); ny tråd (firstUser) .start (); ny tråd (secondUser) .start ();

Etter å ha kjørt denne koden, vil vi se på standardutgangen som Trådlokal ble satt per gitt tråd:

trådkontekst for gitt brukerId: 1 er: Kontekst {userNameSecret = '18a78f8e-24d2-4abf-91d6-79eaa198123f'} trådkontekst for gitt brukerId: 2 er: Kontekst {userNameSecret = 'e19f6a0a-253e-423e-8b2b-bca1f1}

Vi kan se at hver av brukerne har sin egen Kontekst.

5. Trådlokals og trådbassenger

Trådlokal gir et brukervennlig API for å begrense noen verdier til hver tråd. Dette er en rimelig måte å oppnå trådsikkerhet på Java. Derimot, vi bør være ekstra forsiktige når vi bruker Trådlokals og trådbassenger sammen.

For å bedre forstå den mulige advarselen, la oss vurdere følgende scenario:

  1. Først låner applikasjonen en tråd fra bassenget.
  2. Deretter lagrer den noen trådbegrensede verdier i gjeldende tråd Trådlokal.
  3. Når den nåværende kjøringen er ferdig, returnerer applikasjonen den lånte tråden til bassenget.
  4. Etter en stund låner applikasjonen den samme tråden for å behandle en ny forespørsel.
  5. Siden applikasjonen ikke utførte de nødvendige oppryddingene sist, kan det bruke det samme på nytt Trådlokal data for den nye forespørselen.

Dette kan føre til overraskende konsekvenser i applikasjoner som er svært samtidige.

En måte å løse dette problemet på er å fjerne hver manuelt Trådlokal når vi er ferdig med å bruke den. Fordi denne tilnærmingen trenger strenge kodevurderinger, kan den være feilutsatt.

5.1. Utvide ThreadPoolExecutor

Som det viser seg, det er mulig å utvide ThreadPoolExecutor klasse og gi en tilpasset krokimplementering for beforeExecute () og afterExecute () metoder. Trådbassenget vil kalle beforeExecute () metoden før du kjører noe ved hjelp av den lånte tråden. På den annen side vil den kalle afterExecute () metode etter å ha utført logikken vår.

Derfor kan vi utvide ThreadPoolExecutor klasse og fjern Trådlokal data i afterExecute () metode:

offentlig klasse ThreadLocalAwareThreadPool utvider ThreadPoolExecutor {@Override beskyttet ugyldig etterExecute (Runnable r, Throwable t) {// Call remove on each ThreadLocal}}

Hvis vi sender inn våre forespørsler til denne implementeringen av ExecutorService, så kan vi være sikre på at bruk Trådlokal og trådbassenger vil ikke innføre sikkerhetsfarer for applikasjonen vår.

6. Konklusjon

I denne raske artikkelen så vi på Trådlokal konstruere. Vi implementerte logikken som bruker ConcurrentHashMap som ble delt mellom tråder for å lagre konteksten knyttet til et bestemt bruker-ID. Deretter skrev vi om eksemplet vårt for å utnytte Trådlokal å lagre data som er knyttet til et bestemt bruker-ID og med en bestemt tråd.

Implementeringen av alle disse eksemplene og kodebiter finner du på GitHub.


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