Кратко
В комментариях к исходному коду jdk есть такое описание:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
Этот класс предоставляет локальные переменные потока. Эти переменные отличаются от своих обычных аналогов, потому что каждый поток, обращающийся к ним (через свои методы get или set), имеет свою собственную независимо инициализированную копию переменной. Экземпляры ThreadLocal обычно представляют собой закрытые статические поля в классах, которые хотят связать состояние с потоками (например, идентификатор пользователя или идентификатор транзакции).
Должно быть ясно, что ThreadLocal используется не для решения проблемы общих переменных и не для координации синхронизации потоков, а для облегчения обработки каждым потоком своего состояния.
Пример использования ThreadLocal
public class SeqCount {
private static ThreadLocal seqCount = new ThreadLocal(){
// 实现initialValue()
public Integer initialValue() {
return 0;
}
};
public int nextSeq(){
seqCount.set(seqCount.get() + 1);
return seqCount.get();
}
public void remove() {
seqCount.remove();
}
public static void main(String[] args){
SeqCount seqCount = new SeqCount();
SeqThread thread1 = new SeqThread(seqCount);
SeqThread thread2 = new SeqThread(seqCount);
SeqThread thread3 = new SeqThread(seqCount);
SeqThread thread4 = new SeqThread(seqCount);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
private static class SeqThread extends Thread{
private SeqCount seqCount;
SeqThread(SeqCount seqCount){
this.seqCount = seqCount;
}
public void run() {
try {
for(int i = 0 ; i < 3 ; i++){
System.out.println(Thread.currentThread().getName() + " seqCount :" + seqCount.nextSeq());
}
} finally {
seqCount.remove();
}
}
}
}
результат операции:
Thread-0 seqCount :1
Thread-0 seqCount :2
Thread-0 seqCount :3
Thread-1 seqCount :1
Thread-1 seqCount :2
Thread-1 seqCount :3
Thread-3 seqCount :1
Thread-3 seqCount :2
Thread-3 seqCount :3
Thread-2 seqCount :1
Thread-2 seqCount :2
Thread-2 seqCount :3
Из результатов видно, чтоThreadLocal действительно является механизмом изоляции потоков, обеспечивающим безопасность переменных.
Принцип реализации ThreadLocal
ThreadLocal создает отдельную копию переменной для каждого потока, поэтому каждый поток может независимо изменять копию переменной, которой он владеет, не затрагивая копии, соответствующие другим потокам.Начните с нескольких методов
установить метод
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 通过当前线程实例获取ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 若map不为null,则以当前threadLocal为键,value为值存放
if (map != null)
map.set(this, value);
// 若map为null,则创建ThreadLocalMap,以当前threadLocal为键,value为值
else
createMap(t, value);
}
Получите текущий экземпляр потока, вызовите getMap(), чтобы получить ThreadLocalMap этого потока
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Затем оцените, является ли карта нулевой. Если она нулевая, вам нужно создать threadLocalMap с текущим threadLocal в качестве ключа и значением в качестве значения, которое хранится в threadLocalMap. Если это не нуль, он может храниться напрямую.
получить метод
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取线程关联的ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 若map不为null,从map中获取以当前threadLocal实例为key的数据
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 若map为null或者entry为null,则调用此方法初始化
return setInitialValue();
}
Метод get получает ThreadLocalMap, связанный с текущим потоком. Если карта не нулевая, используйте экземпляр threadLocal в качестве ключа для получения данных; если карта нулевая или запись нулевая, вызовите метод setInitialValue()
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
Аналогичен методу set, но с дополнительным методом initialValue(), который необходимо переписать в подклассах.
protected T initialValue() {
return null;
}
метод удаления
public void remove() {
// 根据当前线程获取其所关联的ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
// 若map不为null,删除以当前threadLocal为key的数据
if (m != null)
m.remove(this);
}
ThreadLocalMap
Из основных методов ThreadLocal его реализация основана на внутреннем классе ThreadLocalMap.
Свойство ThreadLocalMap
// 初始化容量
private static final int INITIAL_CAPACITY = 16;
// 哈希表
private Entry[] table;
// 元素个数
private int size = 0;
// 扩容阈值(threshold = 底层哈希表table的长度 len * 2 / 3)
private int threshold;
запись внутреннего класса
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
Из исходного кода мы можем знать, что ключ Entry — Threadlocal, а Entry наследует слабую ссылку WeakReference. Обратите внимание, что в Entry нет атрибута next.По сравнению с HashMap, для обработки конфликтов используется метод адресации по цепочке.ThreadLocalMap использует открытый метод адресации
установить метод
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 根据ThreadLocal的hashcode值,寻找对应Entry在数组中的位置
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
// 若找到对应key,替换旧值返回
if (k == key) {
e.value = value;
return;
}
// 若key == null,因为e!=null肯定存在entry
// 说明之前的ThreadLocal对象已经被回收
if (k == null) {
// 替换旧entry
replaceStaleEntry(key, value, i);
return;
}
}
// 创建新entry
tab[i] = new Entry(key, value);
// 元素个数+1
int sz = ++size;
// cleanSomeSlots 清除旧Entry(key == null)
// 如果没有要清除的数据,元素个数仍然大于阈值则扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
Каждый объект ThreadLocal имеет хеш-значение threadLocalHashCode, и каждый раз, когда объект ThreadLocal инициализируется, хэш-значение увеличивается на фиксированный размер 0x61c88647.Во время процесса вставки сначала найдите расположение хеш-таблицы в соответствии с хэш-значением объекта threadlocal:
1. Если это место пусто, создайте объект Entry и поместите его в это место, а затем вызовите метод cleanSomeSlots(), чтобы очистить старую запись с нулевым ключом. обязательный.
2. Если в этом месте уже есть объект Entry, если ключ объекта Entry точно соответствует ключу, который необходимо установить, или ключ имеет значение null, замените значение value
3. Если ключ объекта Entry в этом месте не соответствует условиям, ищем это место в хеш-таблице + 1 (если достигнут конец хэш-таблицы, начинаем с начала)
Мы можем обнаружить, что ThreadLocalMap использует открытый метод адресации для разрешения конфликтов,Как только произойдет столкновение, найдите следующий пустой хеш-адрес., а HashMap использует метод цепочек адресов для разрешения конфликтов и использует обработку связанных списков в исходной позиции.
метод getEntry
private Entry getEntry(ThreadLocal key) {
// 定位
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 若此位置不为空且与entry的key返回entry对象
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
Поймите set, getEntry легко понять. Во-первых, найдите расположение хеш-таблицы в соответствии с хэш-значением объекта threadlocal. Если ключ записи в этом месте совпадает с искомым ключом, запись будет возвращена напрямую. Если он не совпадает, вызовите getEntryAfterMiss(), чтобы продолжить поиск в обратном направлении. Метод getEntryAfterMiss выглядит следующим образом:
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal k = e.get();
// 找到和所需key相同的entry则返回
if (k == key)
return e;
// 处理key为null的entry
if (k == null)
expungeStaleEntry(i);
else
// 继续找下一个
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
метод удаления
private void remove(ThreadLocal key) {
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)]) {
// 若找到所需key
if (e.get() == key) {
// 将entry的key置为null
e.clear();
// 将entry的value置为null同时entry置空
expungeStaleEntry(i);
return;
}
}
}
Найдите расположение хеш-таблицы, найдите запись с тем же ключом, вызовите метод очистки, чтобы установить ключ в значение null, вызовите метод expungeStaleEntry, чтобы удалить объект с истекшим сроком действия в соответствующем месте, и удалите объект с ключом = null после это место
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 将此位置的entry对象置空以及value置空
tab[staleSlot].value = null;
tab[staleSlot] = null;
// 元素个数-1
size--;
// Rehash until we encounter null
Entry e;
int i;
// 清除此位置后key为null的entry对象以及rehash位置不同的entry直至有位置为空为止
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
утечка памяти
Сначала прикрепите четыре вида ссылок и отношений gc.
тип ссылки | механизм рециркуляции | использовать | время выживания |
сильная цитата | никогда не перерабатывать | состояние объекта | Когда JVM перестает работать |
мягкая ссылка | Перезапустить, когда памяти мало | кеш объектов | Завершить, когда не хватает памяти |
слабая ссылка | Переработано, когда на объект нет ссылки | кеш объектов | Завершить после GC |
фантомная ссылка | Переработано, когда на объект нет ссылки | Сборка мусора отслеживаемых объектов | Завершить после сборки мусора |
Son son = new Son();
Parent parent = new Parent(son);
Когда мы очищаем сына, поскольку родитель содержит ссылку на сына, а родитель является строгой ссылкой, сборщик мусора не восстанавливает пространство памяти, выделенное сыном, что приводит к утечке памяти.Если это слабая ссылка, в приведенном выше примере сборщик мусора вернет пространство памяти, выделенное сыном. ThreadLocalMap использует слабую ссылку ThreadLocal в качестве ключа.Хотя ThreadLocal является слабой ссылкой, GC вернет эту часть пространства, то есть ключ будет переработан, но значение имеет сильную цепочку ссылок из Current Thread. Таким образом, только когда текущий поток уничтожен, значение может освободиться
Так как же эффективно избежать этого?
В приведенном выше примере мы видим, что метод set/getEntry в ThreadLocalMap будет определять, является ли ключ нулевым (то есть ThreadLocal равен нулю).Если он равен нулю, то значение будет установлено равным нулю. Конечно, его также можно освободить, вызвав метод удаления ThreadLocal.
Суммировать
ThreadLocal не используется для решения проблемы многопоточного доступа к общим объектам, а представляет собой механизм, облегчающий каждому потоку обработку своего собственного состояния. Он предоставляет каждому потоку копию переменной, обеспечивая одновременный доступ, не влияя друг на друга. Кроме того, у ThreadLocal может быть проблема с утечкой памяти.После использования ThreadLocal лучше вызвать метод удаления.
благодарный
у-у-у. Краткое описание.com/fear/377baby8408…
woohoo.brief.com/afraid/ohoh8 из 9DCCC…
cmsblogs.com/?p=2442