Сегодня вы узнаете о ThreadLocal — одном из распространенных классов, используемых при разработке Java-приложений.

Что такое ThreadLocal?

Класс ThreadLocal хранит локальные переменные для потоков. Эти переменные изолированы между разными потоками и могут быть доступны только своему собственному потоку. Варианты применения ThreadLocal:
  1. Изоляция данных между потоками.
  2. Управление сессиями для подключения к базе данных.
  3. Хранение транзакционной информации потока.
Кофе-брейк #160. Глубокое погружение в Java ThreadLocal. Класс Scanner в Java - 1

Как использовать ThreadLocal?

Давайте рассмотрим простой пример.

public static void main(String[] args) {
    //Создаем ThreadLocal
    ThreadLocal<String> local = new ThreadLocal<>();
    //Создаем новый класс Random
    Random random = new Random();
    //Создаем 5 потоков
    IntStream.range(0, 5).forEach(a-> new Thread(()-> {
        //Присваиваем значение каждому потоку
        local.set(a+"  "+random.nextInt(100));
        System.out.println("Thread number and its local value  "+ local.get());
    }).start());
}
В приведенном выше коде мы создаем класс ThreadLocal, создаем 5 потоков, присваиваем значение ThreadLocal в каждом потоке и выводим на печать. При выводе получаем: Кофе-брейк #160. Глубокое погружение в Java ThreadLocal. Класс Scanner в Java - 2

Что под капотом?

Если посмотреть внимательно, то можно понять, что в ThreadLocal из этого примера кода есть два важных метода.
  • public T get() {}

  • public void set (T value) {}

Давайте посмотрим на метод setter в исходном коде ThreadLocal:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}
Метод setter сначала получает текущий поток и вызывает метод getMap() для получения класса ThreadLocalMap. Если map существует, возьмите текущий поток t в качестве ключа, входной параметр в качестве значения и установите пару {key:value} в map. Если нет, то создайте map. Теперь у вас может появиться вопрос — что такое ThreadLocalMap?

static class ThreadLocalMap {
   /**
    * The entries in this hash map extend WeakReference, using
    * its main ref field as the key (which is always a
    * ThreadLocal object).  Note that null keys (i.e. entry.get()
    * == null) mean that the key is no longer referenced, so the
    * entry can be expunged from table.  Such entries are referred to
    * as "stale entries" in the code that follows.
    */
    static class Entry extends WeakReference<ThreadLocal<?>> {
       /** The value associated with this ThreadLocal. */
       Object value;
       Entry(ThreadLocal<?> k, Object v) {
           super(k);
           value = v;
       }
    }
}
ThreadLocalMap — это внутренний статический класс в ThreadLocal, определяющий класс Entry для хранения данных. Entry использует экземпляр ThreadLocal в качестве ключа и устанавливает значение, которое мы передаем. Если на данном этапе это звучит слишком запутанно, просто запомните, что именно класс Entry в ThreadLocalMap выполняет фактическое хранение значений. Чтобы получить данные из ThreadLocal, мы используем метод getter:

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
}
В методе getter мы будем использовать currentThread в качестве ключа для получения ThreadLocalMap. Затем map получит getEntry() на основе экземпляра ThreadLocal и вернет экземпляр Entry, а затем сохраненное значение. Вот схема, которая поможет разобраться: Кофе-брейк #160. Глубокое погружение в Java ThreadLocal. Класс Scanner в Java - 3Кофе-брейк #160. Глубокое погружение в Java ThreadLocal. Класс Scanner в Java - 4
  1. Каждый поток поддерживает ссылку на ThreadLocalMap.

  2. ThreadLocalMap является внутренним статическим классом ThreadLocal и использует класс Entry для хранения.

  3. Ключ ThreadLocalMap является экземпляром ThreadLocal и может иметь несколько ThreadLocal.

  4. Сам ThreadLocal не хранит значение, но это ключ для потока, который поможет получить значение из ThreadLocalMap.

Обратите внимание, что лучше удалить ThreadLocal, чтобы избежать OOM (Out-of-Memory Error, ошибка нехватки памяти) из-за “слабой” ссылки в классе Entry.