Een inleiding tot ThreadLocal in Java

1. Overzicht

In dit artikel zullen we kijken naar de ThreadLocal construeren vanuit de java.lang pakket. Dit geeft ons de mogelijkheid om gegevens afzonderlijk op te slaan voor de huidige thread - en deze eenvoudig in een speciaal type object te wikkelen.

2. ThreadLocal API

De TheadLocal construct stelt ons in staat om gegevens op te slaan die zullen worden alleen toegankelijk door een specifieke draad.

Laten we zeggen dat we een Geheel getal waarde die wordt gebundeld met de specifieke thread:

ThreadLocal threadLocalValue = nieuwe ThreadLocal ();

Als we vervolgens deze waarde van een thread willen gebruiken, hoeven we alleen een krijgen() of set () methode. Simpel gezegd, we kunnen dat denken ThreadLocal slaat gegevens op in een kaart - met de draad als sleutel.

Vanwege dat feit, wanneer we een krijgen() methode op de threadLocalValue, we krijgen een Geheel getal waarde voor de vragende thread:

threadLocalValue.set (1); Integer resultaat = threadLocalValue.get ();

We kunnen een instantie van het ThreadLocal door de withInitial () statische methode en het doorgeven van een leverancier eraan:

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

Om de waarde te verwijderen uit het ThreadLocal, kunnen we de verwijderen() methode:

threadLocal.remove ();

Om te zien hoe u het ThreadLocal goed, eerst zullen we kijken naar een voorbeeld dat geen gebruik maakt van een ThreadLocal, dan zullen we ons voorbeeld herschrijven om die constructie te benutten.

3. Gebruikersgegevens opslaan op een kaart

Laten we eens kijken naar een programma dat het gebruikersspecifieke Context gegevens per opgegeven gebruikers-ID:

openbare klasse Context {privé String gebruikersnaam; openbare context (String gebruikersnaam) {this.userName = gebruikersnaam; }}

We willen één thread per gebruikers-ID hebben. We maken een SharedMapWithUserContext klasse die de Runnable koppel. De implementatie in het rennen() methode roept een database aan via de UserRepository klasse die een Context object voor een gegeven gebruikersnaam.

Vervolgens slaan we die context op in de ConcurentHashMap ingetoetst door gebruikersnaam:

openbare klasse SharedMapWithUserContext implementeert Runnable {openbare statische kaart userContextPerUserId = nieuwe ConcurrentHashMap (); private Integer userId; private UserRepository userRepository = nieuwe UserRepository (); @Override public void run () {String userName = userRepository.getUserNameForUserId (userId); userContextPerUserId.put (userId, new Context (userName)); } // standaard constructor}

We kunnen onze code eenvoudig testen door twee threads te maken en te starten voor twee verschillende userIds en beweren dat we twee vermeldingen hebben in de userContextPerUserId kaart:

SharedMapWithUserContext firstUser = nieuwe SharedMapWithUserContext (1); SharedMapWithUserContext secondUser = nieuwe SharedMapWithUserContext (2); nieuwe Thread (firstUser) .start (); nieuwe thread (secondUser) .start (); assertEquals (SharedMapWithUserContext.userContextPerUserId.size (), 2);

4. Gebruikersgegevens opslaan in ThreadLocal

We kunnen ons voorbeeld herschrijven om de gebruiker op te slaan Context instantie met behulp van een ThreadLocal. Elke thread heeft zijn eigen ThreadLocal voorbeeld.

Tijdens gebruik ThreadLocal, we moeten heel voorzichtig zijn, want elk ThreadLocal instantie is geassocieerd met een bepaalde thread. In ons voorbeeld hebben we een speciale thread voor elk specifiek onderwerp gebruikersnaam, en deze thread is door ons gemaakt, dus we hebben er volledige controle over.

De rennen() methode haalt de gebruikerscontext op en slaat deze op in de ThreadLocal variabele met behulp van de set () methode:

openbare klasse ThreadLocalWithUserContext implementeert Runnable {privé statische ThreadLocal userContext = nieuwe ThreadLocal (); private Integer userId; private UserRepository userRepository = nieuwe UserRepository (); @Override public void run () {String userName = userRepository.getUserNameForUserId (userId); userContext.set (nieuwe context (gebruikersnaam)); System.out.println ("thread context voor gegeven userId:" + userId + "is:" + userContext.get ()); } // standaard constructor}

We kunnen het testen door twee threads te starten die de actie voor een gegeven uitvoeren gebruikersnaam:

ThreadLocalWithUserContext firstUser = nieuwe ThreadLocalWithUserContext (1); ThreadLocalWithUserContext secondUser = nieuwe ThreadLocalWithUserContext (2); nieuwe Thread (firstUser) .start (); nieuwe thread (secondUser) .start ();

Na het uitvoeren van deze code zullen we dat op de standaarduitvoer zien ThreadLocal werd ingesteld per gegeven thread:

threadcontext voor gegeven userId: 1 is: Context {userNameSecret = '18a78f8e-24d2-4abf-91d6-79eaa198123f'} threadcontext voor gegeven userId: 2 is: Context {userNameSecret = 'e19f6a0a-253e-423e-8b2b-bca1f}

We kunnen zien dat elk van de gebruikers zijn eigen heeft Context.

5. ThreadLocals en Thread Pools

ThreadLocal biedt een gebruiksvriendelijke API om enkele waarden tot elke thread te beperken. Dit is een redelijke manier om thread-veiligheid in Java te bereiken. Echter, we moeten extra voorzichtig zijn wanneer we gebruiken ThreadLocals en thread pools samen.

Laten we het volgende scenario bekijken om het mogelijke voorbehoud beter te begrijpen:

  1. Ten eerste leent de applicatie een thread uit de pool.
  2. Vervolgens worden enkele thread-beperkte waarden opgeslagen in de huidige thread ThreadLocal.
  3. Zodra de huidige uitvoering is voltooid, retourneert de applicatie de geleende thread naar de pool.
  4. Na een tijdje leent de applicatie dezelfde thread om een ‚Äč‚Äčander verzoek te verwerken.
  5. Omdat de applicatie de vorige keer niet de nodige opruimingen heeft uitgevoerd, kan deze hetzelfde opnieuw gebruiken ThreadLocal gegevens voor het nieuwe verzoek.

Dit kan verrassende gevolgen hebben in zeer gelijktijdige toepassingen.

Een manier om dit probleem op te lossen, is door elk ThreadLocal als we het eenmaal hebben gebruikt. Omdat deze aanpak rigoureuze codebeoordelingen vereist, kan deze foutgevoelig zijn.

5.1. Uitbreiding van het ThreadPoolExecutor

Zoals het blijkt, het is mogelijk om het ThreadPoolExecutor class en zorg voor een aangepaste hook-implementatie voor beforeExecute () en afterExecute () methoden. De thread-pool roept het beforeExecute () methode voordat u iets uitvoert met behulp van de geleende thread. Aan de andere kant zal het de afterExecute () methode na het uitvoeren van onze logica.

Daarom kunnen we de ThreadPoolExecutor class en verwijder het ThreadLocal gegevens in de afterExecute () methode:

openbare klasse ThreadLocalAwareThreadPool breidt ThreadPoolExecutor uit {@Override protected void afterExecute (Runnable r, Throwable t) {// Call remove on each ThreadLocal}}

Als we onze verzoeken indienen bij deze uitvoering van ExecutorService, dan kunnen we er zeker van zijn dat het gebruik van ThreadLocal en draadpools zullen geen veiligheidsrisico's voor onze toepassing introduceren.

6. Conclusie

In dit korte artikel keken we naar de ThreadLocal construeren. We hebben de logica geïmplementeerd die gebruikt ConcurrentHashMap dat werd gedeeld tussen threads om de context op te slaan die aan een bepaald gebruikersnaam. Vervolgens hebben we ons voorbeeld herschreven om te profiteren ThreadLocal om gegevens op te slaan die zijn gekoppeld aan een bepaald gebruikersnaam en met een bepaalde draad.

De implementatie van al deze voorbeelden en codefragmenten is te vinden op GitHub.