ThreadLocal – co to takiego?

ThreadLocal jest klasą pozwalającą na łatwe przechowywanie wartości dla każdego wątku oddzielnie – każdy wątek ma swoją wartość.

Korzystanie z klasy ThreadLocal

Klasa ThreadLocal udostępnia podstawowe operacje takie jak:
get()
set(T value)
remove()
Dzięki którym możemy przykładowo operować na wartości przypisanej do wątku:

@Test
public void testBasics(){
    ThreadLocal<String> threadLocal = new ThreadLocal<>();
    threadLocal.set("Dog");
    assertEquals("Dog", threadLocal.get());
    threadLocal.remove();
    assertNull(threadLocal.get());
}

Jednak w sytuacji gdy mamy tylko jeden wątek nie ma najmniejszego sensu używanie klasy ThreadLocal. Lepiej po prostu użyć zmiennych:

@Test
public void testBasics_simplified(){
    String value;
    value = "Dog";
    assertEquals("Dog", value);
    value = null;
    assertNull(value);
}

Żeby użycie ThreadLocal miało sens, potrzeba jest przynajmniej dwóch wątków:

@Test
public void testTwoThreads() throws InterruptedException {
    final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    threadLocal.set("Dog");
    Thread thread = new Thread(() -> threadLocal.set("Cat"));
    thread.start();
    thread.join();
    assertEquals("Dog", threadLocal.get());
}

Jako, że nadpisywanie (patrz threadLocal.set(„Cat”)) wartości odbywa się w oddzielnym wątku, nie wpływa to na wartość ustawioną w wątku wywołującym (patrz threadLocal.set(„Dog”)).

Krok głębiej

Warto zajrzeć do implementacji klasy ThreadLocal, aby lepiej zrozumieć jej działanie (wycinki poniżej zawierają uproszczone wersje oryginalnej klasy).

get()

public T get() {
    return get(Thread.currentThread());
}

Jak widać implementacja metody get jest prosta i opiera się o wywołanie metody get przekazując do niej aktualny wątek (Thread.currentThread()):

private T get(Thread t) {
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //pobieranie wartosci z mapy
    }
    return setInitialValue(t);
}

Jak widać wartości dla danego wątku są przechowywane w specjalnej mapie – ThreadLocalMap. I jeśli taka mapa istnieje to następuje pobranie wartości z tej mapy. W innym wypadku ustawiana jest wartość początkowa dla danego wątku.

set(T value)

public void set(T value) {
    set(Thread.currentThread(), value);
    //...
}

Podobnie jak metoda get, metoda set korzysta z aktualnego wątku (Thread.currentThread()). I ustawia w nim wartość za pomocą metody set przyjmującej oprócz wartości, wątek którego ma dotyczyć wartość:

private void set(Thread t, T value) {
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
       createMap(t, value);
    }
}

Jak widać implementacja metody set jest bardzo prosta – jeśli wartość już istnieje, to ją nadpisujemy. Jeśli nie to tworzymy nową z zadaną wartością. I znowu podobnie jak w metodzie get, do obsługi wartości wykorzystywana jest mapa – ThreadLocalMap.

remove()

public void remove() {
    remove(Thread.currentThread());
}

Metoda remove działa identycznie do poprzednich – pobiera aktualny wątek i przekazuje go do metody przyjmującej jako argument wątek:

private void remove(Thread t) {
    ThreadLocalMap m = getMap(t);
    if (m != null) {
        m.remove(this);
    }
}

I znowu, logika jest tutaj bardzo prosta – jeśli już jest w mapie wartość dla tego wątku to ją usuń, jeśli nie to po prostu nic nie rób (wszystko cały czas oparte o mapę – ThreadLocalMap).

Warto zajrzeć

1. Przykłady z wpisu
2. Dokumentacja
2. Kod źródłowy

Pozostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *