Принципиальный анализ Java ThreadLocal

Java

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

Введение в использование ThreadLocal

Например, есть такой класс:

public class Counter{
	private int count;
 	public Counter(int cnt) {
            this.count = cnt;
    }
	//省略setter()和getter()方法
}

Мы надеемся, что когда несколько потоков обращаются к объекту Counter, каждый поток сохраняет счетчик счетчика, который можно записать следующим образом:

ThreadLocal<Counter> threadLocal = new ThreadLocal<>();
threadLocal.set(new Counter(0));
Counter counter = threadLocal.get();

Если вы не хотите инициализировать каждый раз, когда вы его вызываете, вы можете переопределить метод initValue() ThreadLocal, чтобы установить начальное значение объекта для ThreadLocal следующим образом:

ThreadLocal<Counter> threadLocal = new ThreadLocal<Counter>() {
    @Override
    protected Counter initialValue() {
        return new Counter(0);
    }
};

Таким образом, каждый раз, когда вызывается threadLocal.get(), он определяет, есть ли в текущем потоке объект Counter, и, если нет, вызывает метод initValue() для его инициализации.

Принцип реализации ThreadLocal

Давайте сначала посмотрим на метод get() 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();
}

При вызове метода get() сначала получается текущий поток, а затем через поток получается объект ThreadLocalMap, привязанный к каждому потоку.Ключом в карте является объект ThreadLocal, а значением является фактический объект, который мы хотим Доступ. В приведенном выше примере это объект Counter. Если Entry пуст, вызовите метод setInitialValue(), чтобы установить начальное значение.

Принцип реализации ThreadLocalMap

Как видно спереди, внутри каждого потока есть ThreadLocalMap.Из исходного кода класса java.lang.Thread вы также можете видеть, что класс Thread имеетThreadLocal.ThreadLocalMap threadLocalsсвойства, разберем принцип его реализации:
Реализация ThreadLocalMap и WeakHashMap в чем-то похожа, а также использует WeakReference для установления связи с GC, поскольку на объекты ThreadLocal ссылаются объекты потока, если поток имеет длительный жизненный цикл, может возникнуть проблема с утечкой памяти, ThreadLocalMap умно решает это со слабыми ссылками. Чтобы решить эту проблему, исходный код выглядит следующим образом:

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
		//这里的Entry并不是一个链表,如果出现hash碰撞,会放到数组的下一个位置
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
}

Из исходного кода мы видим, что Entry ThreadLocalMap наследуется от WeakReference. Ключ в Entry является слабой ссылкой, то есть класс потока содержит слабую ссылку на объект ThreadLocal. Когда у объекта ThreadLocal нет других сильная ссылочная ассоциация, она будет использоваться во время GC Recycle объекта ThreadLocal, но здесь перерабатывается только объект ThreadLocal. Объект Entry и Value не перерабатываются. В ThreadLocalMap может быть много Entry с нулевым ключом, поэтому необходимо вызвать map.getEntry() для объекта, ключ которого равен null.Обработка, исходный код выглядит следующим образом:

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
		//当前位置没有找到,可能Hash碰撞了
        return getEntryAfterMiss(key, i, e);
}

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
			//找到了直接返回
            return e;
        if (k == null)
			//检测到有个ThreadLocal对象被回收了,这个时候去清理后面所有Key为null的Entry
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

Из исходного кода видно, что в момент, когда ThreadLocalMap очищает запись, ключ которой равен Null, происходит коллизия хэшей, когда запись получается в соответствии с хэш-кодом объекта ThreadLocal, а следующая запись очищается, когда ключ имеет значение null, потому что запись ThreadLocalMap отличается от обычных карт. Общая карта представляет собой массив связанного списка, и каждый элемент его массива является записью. Если есть коллизия хэшей, она будет помещена в следующая позиция массива.Поэтому, если при получении нет коллизии, можно считать, что в текущей карте не так много элементов.После обнаружения коллизии и восстановления ключа следующей записи, expungeStaleEntry() вызывается для освобождения тех записей, чей ThreadLocal равен нулю, избегая утечек памяти.

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // expunge entry at staleSlot
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // Rehash until we encounter null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        if (k == null) {
			//清理key为null的entry
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
			//hashCode发生了变化,重新放置entry
            if (h != i) {
                tab[i] = null;
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}

Суммировать

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