Интервью по Java нужно спрашивать, окончательная статья ThreadLocal

интервью Java задняя часть API

счет за волкаПожалуйста, указывайте первоисточник при перепечатке, спасибо!

предисловие

В процессе собеседования интервьюер также часто проверяет «ThreadLocal», поэтому очень важно досконально его понять.

Некоторые интервьюеры задают прямые вопросы:

  • "Знаете ThreadLocal?"
  • «Расскажите, как вы понимаете ThreadLocal»

Конечно, есть и интервьюеры, которые медленно ведут к этой теме, например, спрашивая «в многопоточной среде, как предотвратить подделку моих переменных другими потоками», отдавайте инициативу себе и полагайтесь на себя. играть в остальное.

Так что же может сделать ThreadLocal? Прежде чем понять сценарий его применения, давайте взглянем на принцип его реализации. Только зная принцип реализации, мы можем судить, соответствует ли он нашему собственному бизнес-сценарию.

Что такое ThreadLocal

Во-первых, это структура данных, немного похожая на HashMap, которая может сохранять пары ключ-значение «ключ: значение», но ThreadLocal может сохранять только одну, и данные каждого потока не мешают друг другу.

ThreadLocal<String> localName = new ThreadLocal();
localName.set("占小狼");
String name = localName.get();

Объект ThreadLocal localName инициализируется в потоке 1, и значение сохраняется с помощью метода set.占小狼, при передаче в потоке 1localName.get()Вы можете получить ранее установленное значение, но если вы находитесь в потоке 2, вы получите нулевое значение.

Почему и как? Но, как я уже говорил, ThreadLocal гарантирует, что данные каждого потока не будут мешать друг другу.

Взгляниset(T value)иget()исходный код метода

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

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

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

Можно обнаружить, что каждая нить имеетThreadLocalMapСтруктура данных, при выполнении метода set его значение сохраняется в текущем потокеthreadLocalsВ переменной при выполнении метода set он из текущего потокаthreadLocalsпеременное приобретение.

Следовательно, значение, установленное в потоке 1, не может быть затронуто потоком 2, и если оно будет сброшено в потоке 2, это не повлияет на значение в потоке 1, гарантируя, что потоки не будут мешать друг другу.

что в каждом тредеThreadLoalMapЧто именно?

ThreadLoalMap

В этой статье анализируется исходный код версии 1.7.

Из названия можно догадаться, что это тоже структура данных, похожая на HashMap, но в ThreadLocal не реализован интерфейс Map.

В ThreadLoalMap также инициализируется массив Entry размером 16. Объект Entry используется для сохранения каждой пары ключ-значение, но ключом здесь всегда является объект ThreadLocal. Разве это не удивительно? С помощью метода set объект ThreadLocal. В результате сам объект ThreadLocal используется в качестве ключа и помещается в ThreadLoalMap.

Здесь следует отметить, что Entry ThreadLoalMap наследует WeakReference, а большое отличие от HashMap заключается в том, что в Entry нет поля next, поэтому нет связанного списка.

хэш-коллизия

Без структуры связанного списка, что мне делать, если возникнет конфликт хэшей?

Сначала посмотрите на реализацию вставки ключ-значение в ThreadLoalMap

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

Каждый объект ThreadLocal имеет хэш-значениеthreadLocalHashCode, каждый раз, когда объект ThreadLocal инициализируется, хэш-значение увеличивается на фиксированный размер0x61c88647.

В процессе вставки по хеш-значению объекта ThreadLocal находится позиция i в таблице Процесс выглядит следующим образом: 1. Если текущая позиция пуста, то в самый раз, инициализируйте объект Entry и поместите его в позицию i; 2. К сожалению, объект Entry уже есть в позиции i. Если ключ этого объекта Entry окажется ключом, который нужно установить, то сбросить значение в Entry; 3. К сожалению, объект Entry в позиции i не имеет ничего общего с устанавливаемым ключом, поэтому можно найти только следующую пустую позицию;

В этом случае при получении он также будет определять позицию в таблице в соответствии с хэш-значением объекта ThreadLocal, а затем судить, согласуется ли ключ в объекте Entry в этой позиции с ключом получения, и если это непоследовательно, судите по следующей позиции.

Можно обнаружить, что если конфликт между set и get серьезный, эффективность очень низкая, потому что ThreadLoalMap является атрибутом Thread, поэтому даже если вы контролируете количество элементов, установленных в своем собственном коде, вы все равно не можете контролировать поведение других кодов.

утечка памяти

ThreadLocal может вызвать утечку памяти, почему? Первый взгляд на реализацию Entry:

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

Из предыдущего анализа было известно, что при использовании ThreadLocal для сохранения значения объект Entry будет вставлен в массив в ThreadLocalMap.Разумеется, что ключ-значение должно храниться в объекте Entry со строгой ссылкой, но в реализации ThreadLocalMap ключ сохраняется в объекте WeakReference.

Это приводит к проблеме. ThreadLocal будет переработан при сборке мусора, когда нет внешней строгой ссылки. Если поток, создавший ThreadLocal, продолжает работать, значение в объекте Entry может не перерабатываться постоянно, что приводит к утечкам памяти. .

Как избежать утечек памяти

Поскольку была обнаружена скрытая опасность утечки памяти, существует естественная контрмера: вызов методов get() и set() в ThreadLocal может очистить объект Entry, ключ которого равен null в ThreadLocalMap, так что соответствующее значение будет недоступно для GC Roots. Его можно переработать в следующем сборщике мусора.Конечно, если будет вызван метод удаления, соответствующий объект Entry обязательно будет удален.

Если метод удаления не вызывается явно после использования метода set в ThreadLocal, могут возникнуть утечки памяти, поэтому очень важно выработать хорошие привычки программирования.После использования ThreadLocal не забудьте вызвать метод удаления.

ThreadLocal<String> localName = new ThreadLocal();
try {
    localName.set("占小狼");
    // 其它业务逻辑
} finally {
    localName.remove();
}

Конец я волк Если вы найдете это полезным после прочтения, ставьте лайк и подписывайтесь