ThreadLocal — это один из распространенных классов, используемых в программах Java.

Сегодня я только что столкнулся с вариантом использования, когда кодовая база моего рабочего места использует ThreadLocal для хранения контекста, специфичного для потока.

Но если вы на месте попросите меня подробнее объяснить про ThreadLocal, я, наверное, не смогу на него ответить — поэтому и появилась эта статья.

Для тех, кто берет интервью, я надеюсь, что эта статья также поможет вам, если это тоже один из ваших вопросов на собеседовании.

Что такое ThreadLocal?

Из его имени мы можем сказать, что он хранит локальные переменные для потоков. Эти переменные изолированы между разными потоками и могут быть доступны только своему собственному потоку.

Общие сцены для применения ThreadLocal включают:

  1. Изоляция данных между потоками
  2. Управление сессиями для подключения к базе данных
  3. Хранение транзакционной информации потока

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

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

public static void main(String[] args) {
    //Create a ThreadLocal
    ThreadLocal<String> local = new ThreadLocal<>();
    //Create a new Random class
    Random random = new Random();
    //Create 5 threads
    IntStream.range(0, 5).forEach(a-> new Thread(()-> {
        //Set a local value for each of the threads
        local.set(a+"  "+random.nextInt(100));
        System.out.println("Thread number and its local value  "+ local.get());
    }).start());
}

В приведенном выше коде мы создаем класс ThreadLocal, создаем 5 потоков, присваиваем значение ThreadLocal в каждом потоке и печатаем их.

Вот что выводится:

Достаточно легко?

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

Если вы посмотрите внимательно, то поймете, что в ThreadLocal выше есть два важных метода.

1. public T get() {}
2. public void set (T value) {}

Давайте взглянем на метод установки в исходном коде 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);
}

Метод установки сначала получает текущий поток и вызывает метод getMap() для получения класса ThreadLocalMap. Если карта существует, возьмите текущий поток t в качестве ключа, входной параметр в качестве значения и установите пару {key:value} в карте. Если нет, то создайте карту.

Теперь вы можете задаться вопросом: что такое 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, мы смотрим на метод получения:

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();
}

В методе получения мы будем использовать currentThread в качестве ключа для получения ThreadLocalMap. Затем карта выполнит getEntry() на основе экземпляра ThreadLocal и вернет экземпляр Entry, а затем сохраненное значение.

Чувствуете легкое головокружение?

Может схема поможет:

  1. Каждый Thread содержит ссылку на ThreadLocalMap.
  2. ThreadLocalMap является внутренним статическим классом ThreadLocal и использует класс Entry для хранения
  3. Ключ ThreadLocalMap является экземпляром ThreadLocal и может иметь несколько ThreadLocal.
  4. ThreadLocal сам по себе не хранит значение, но является ключом для потока, чтобы получить значение из ThreadLocalMap

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

Я надеюсь, что вы найдете эту статью полезной.

Я backend инженер-программист. Если вы стремитесь узнать о технологиях, следите за моим каналом и будьте в курсе моих вдохновений из моей повседневной работы и жизни.

Подробнее:
Кейс о статическом ключевом слове Java во время моей работы
Как решить эту проблему многопоточности Java на собеседовании?

Подключайтесь:
Мой LinkedIn