En introduktion til ThreadLocal i Java

1. Oversigt

I denne artikel vil vi se på Trådlokal konstruere fra java.lang pakke. Dette giver os muligheden for at gemme data individuelt til den aktuelle tråd - og simpelthen pakke dem ind i en særlig type objekt.

2. Trådlokal API

Det TheadLocal konstruktion giver os mulighed for at gemme data, der vil være kun tilgængelig ved en bestemt tråd.

Lad os sige, at vi vil have en Heltal værdi, der følger med den specifikke tråd:

ThreadLocal threadLocalValue = ny ThreadLocal ();

Dernæst, når vi vil bruge denne værdi fra en tråd, behøver vi kun kalde en få() eller sæt() metode. Kort sagt kan vi tænke det Trådlokal gemmer data inde på et kort - med tråden som nøgle.

På grund af dette faktum, når vi kalder en få() metode til threadLocalValue, vi får en Heltal værdi for den anmodende tråd:

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

Vi kan konstruere en instans af Trådlokal ved hjælp af withInitial () statisk metode og videregive en leverandør til den:

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

For at fjerne værdien fra Trådlokal, vi kan kalde fjerne() metode:

threadLocal.remove ();

For at se, hvordan du bruger Trådlokal korrekt, for det første vil vi se på et eksempel, der ikke bruger en Trådlokal, så omskriver vi vores eksempel for at udnytte den konstruktion.

3. Lagring af brugerdata på et kort

Lad os overveje et program, der skal gemme den brugerspecifikke Sammenhæng data pr. givet bruger-id:

offentlig klasse Kontekst {privat streng brugernavn; public Context (String userName) {this.userName = userName; }}

Vi vil have en tråd pr. Bruger-id. Vi opretter en SharedMapWithUserContext klasse, der implementerer Kan køres interface. Implementeringen i løb() metoden kalder en eller anden database gennem UserRepository klasse, der returnerer en Sammenhæng objekt for en given bruger ID.

Dernæst gemmer vi denne kontekst i ConcurentHashMap tastet af bruger ID:

offentlig klasse SharedMapWithUserContext implementerer Runnable {public static Map userContextPerUserId = new ConcurrentHashMap (); privat heltal userId; privat UserRepository userRepository = nyt UserRepository (); @Override public void run () {String userName = userRepository.getUserNameForUserId (userId); userContextPerUserId.put (userId, ny kontekst (brugernavn)); } // standardkonstruktør}

Vi kan nemt teste vores kode ved at oprette og starte to tråde til to forskellige userIds og hævder, at vi har to poster i userContextPerUserId kort:

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

4. Lagring af brugerdata i Trådlokal

Vi kan omskrive vores eksempel for at gemme brugeren Sammenhæng eksempel ved hjælp af en Trådlokal. Hver tråd har sin egen Trådlokal eksempel.

Ved brug Trådlokal, vi skal være meget forsigtige, fordi alle Trådlokal forekomst er forbundet med en bestemt tråd. I vores eksempel har vi en dedikeret tråd til hver enkelt bruger ID, og denne tråd er skabt af os, så vi har fuld kontrol over den.

Det løb() metoden henter brugerkonteksten og gemmer den i Trådlokal variabel ved hjælp af sæt() metode:

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

Vi kan teste det ved at starte to tråde, der udfører handlingen for en given bruger ID:

ThreadLocalWithUserContext firstUser = ny ThreadLocalWithUserContext (1); ThreadLocalWithUserContext secondUser = ny ThreadLocalWithUserContext (2); ny tråd (første bruger). start (); ny tråd (secondUser) .start ();

Efter at have kørt denne kode ser vi på standardoutputen, at Trådlokal blev indstillet pr. given tråd:

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

Vi kan se, at hver af brugerne har sin egen Sammenhæng.

5. Trådlokals og trådbassiner

Trådlokal giver en brugervenlig API til at begrænse nogle værdier til hver tråd. Dette er en rimelig måde at opnå trådsikkerhed i Java på. Imidlertid, vi skal være ekstra forsigtige, når vi bruger Trådlokals og trådpuljer sammen.

For bedre at forstå det mulige forbehold, lad os overveje følgende scenarie:

  1. For det første låner applikationen en tråd fra puljen.
  2. Derefter gemmer den nogle trådbegrænsede værdier i de aktuelle tråde Trådlokal.
  3. Når den aktuelle udførelse er færdig, returnerer applikationen den lånte tråd til puljen.
  4. Efter et stykke tid låner applikationen den samme tråd for at behandle en anden anmodning.
  5. Da applikationen ikke udførte de nødvendige oprydninger sidste gang, kan den muligvis genbruge det samme Trådlokal data til den nye anmodning.

Dette kan medføre overraskende konsekvenser i meget samtidige applikationer.

En måde at løse dette problem på er at fjerne hver manuelt Trådlokal når vi er færdige med at bruge det. Fordi denne tilgang kræver strenge kodevurderinger, kan den være udsat for fejl.

5.1. Udvidelse af ThreadPoolExecutor

Det viser sig, det er muligt at udvide ThreadPoolExecutor klasse og give en tilpasset krogimplementering til beforeExecute () og afterExecute () metoder. Trådpuljen kalder beforeExecute () metode, før du kører noget ved hjælp af den lånte tråd. På den anden side vil det kalde afterExecute () metode efter udførelse af vores logik.

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

offentlig klasse ThreadLocalAwareThreadPool udvider ThreadPoolExecutor {@Override beskyttet ugyldigt efterExecute (Runnable r, Throwable t) {// Call remove on each ThreadLocal}}

Hvis vi sender vores anmodninger til denne implementering af ExecutorService, så kan vi være sikre på, at brug af Trådlokal og trådbassiner indebærer ikke sikkerhedsrisici for vores applikation.

6. Konklusion

I denne hurtige artikel så vi på Trådlokal konstruere. Vi implementerede den logik, der bruger ConcurrentHashMap der blev delt mellem tråde for at gemme den sammenhæng, der er knyttet til et bestemt bruger ID. Dernæst omskrev vi vores eksempel til gearing Trådlokal at gemme data, der er knyttet til et bestemt bruger ID og med en bestemt tråd.

Implementeringen af ​​alle disse eksempler og kodestykker findes på GitHub.