Что такое ThreadLocal
Предыдущее интервью на вопрос ThreadLocal всегда заставляло выглядеть невежественным, просто знаю этого парня, не понимаю, что он привык делать, но не знает его принципов. На первый взгляд он и многопоточность, инструменты синхронизации потоков относятся к классу, но на самом деле он не имеет ничего общего с механизмом синхронизации потоков.Механизм синхронизации потоков является множеством потоков, которые имеют одну и ту же переменную, в то время как ThreadLocal создает отдельную переменную копию для каждого потока, каждый поток может изменить свою собственную переменную копию, не влияя на копии, соответствующие другим потокам..
Официальный API описывается следующим образом: Этот класс предоставляет локальные переменные потока. Эти переменные отличаются от своих обычных аналогов тем, что каждый поток, обращающийся к переменной (через свои методы get или set), имеет свою собственную локальную переменную, которая не зависит от инициализированной копии переменной. Экземпляры ThreadLocal обычно представляют собой закрытые статические поля в классах, которые хотят связать состояние с потоком (например, идентификатор пользователя или идентификатор транзакции).
ThreadLocal API
ThreadLocal определяет четыре метода:
- get(): возвращает значение в текущей копии этой локальной переменной потока.
- set(T value): устанавливает значение в текущей копии локальной переменной потока на указанное значение
- initialValue(): возвращает начальное значение в текущей копии этой локальной переменной потока.
- remove(): удаляет значение из текущей копии этой локальной переменной потока.
- ThreadLocal также имеет особенно важный статический внутренний класс ThreadLocalMap, который является ключом к реализации механизма изоляции потоков. get(), set(), remove() основаны на этом внутреннем классе. ThreadLocalMap хранит копию каждой переменной потока в парах "ключ-значение". Ключом является текущий объект ThreadLocal, а значением является копия переменной соответствующий поток.
Представим, что у каждого потока есть свой объект ThreadLocal, то есть у него есть свой ThreadLocalMap.Конечно, операции над своим ThreadLocalMap не влияют друг на друга, так что проблемы безопасности потока нет, поэтому ThreadLocal заменяется космическими решениями безопасности .
Пример использования
Предполагая, что каждому потоку требуется значение счетчика, чтобы записать, сколько раз он что-то делал, и каждый поток должен изменить свое собственное значение счетчика при работе, не затрагивая друг друга, тогда ThreadLocal является хорошим выбором, где текущий хранится в ThreadLocal Копия локальной переменной потока является этим значением счетчика.
public class SeqCount {
private static ThreadLocal<Integer> seqCount = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public int nextSeq() {
seqCount.set(seqCount.get() +1);
return seqCount.get();
}
public static void main(String [] args) {
SeqCount seqCount = new SeqCount();
SeqThread seqThread1 = new SeqThread(seqCount);
SeqThread seqThread2 = new SeqThread(seqCount);
SeqThread seqThread3 = new SeqThread(seqCount);
SeqThread seqThread4 = new SeqThread(seqCount);
seqThread1.start();
seqThread2.start();
seqThread3.start();
seqThread4.start();
}
public static class SeqThread extends Thread {
private SeqCount seqCount;
public SeqThread(SeqCount seqCount) {
this.seqCount = seqCount;
}
@Override
public void run() {
for (int i=0; i<3; i++) {
System.out.println(Thread.currentThread().getName()+" seqCount:"+seqCount.nextSeq());
}
}
}
}
результат операции:
Решить потокобезопасность SimpleDateFormat
Мы знаем, что SimpleDateFormat имеет проблемы с безопасностью потоков при многопоточности, поэтому использование SimpleDateFormat в качестве копии локальной переменной каждого потока означает, что каждый поток имеет свой собственный формат SimpleDateFormat, поэтому проблем с безопасностью потоков нет.
public class SimpleDateFormatDemo {
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<>();
/**
* 获取线程的变量副本,如果不覆盖initialValue方法,第一次get将返回null,故需要创建一个DateFormat,放入threadLocal中
* @return
*/
public DateFormat getDateFormat() {
DateFormat df = threadLocal.get();
if (df == null) {
df = new SimpleDateFormat(DATE_FORMAT);
threadLocal.set(df);
}
return df;
}
public static void main(String [] args) {
SimpleDateFormatDemo formatDemo = new SimpleDateFormatDemo();
MyRunnable myRunnable1 = new MyRunnable(formatDemo);
MyRunnable myRunnable2 = new MyRunnable(formatDemo);
MyRunnable myRunnable3 = new MyRunnable(formatDemo);
Thread thread1= new Thread(myRunnable1);
Thread thread2= new Thread(myRunnable2);
Thread thread3= new Thread(myRunnable3);
thread1.start();
thread2.start();
thread3.start();
}
public static class MyRunnable implements Runnable {
private SimpleDateFormatDemo dateFormatDemo;
public MyRunnable(SimpleDateFormatDemo dateFormatDemo) {
this.dateFormatDemo = dateFormatDemo;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 当前时间:"+dateFormatDemo.getDateFormat().format(new Date()));
}
}
}
результат операции:
Анализ исходного кода
ThreadLocalMap
ThreadLocalMap использует 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 наследует WeakReference, поэтому ссылка на ключ, соответствующий Entry (экземпляр ThreadLocal), является слабой ссылкой.
set(ThreadLocal key, Object value)
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//根据ThreadLocal的散列值,查找对应元素在数组中的位置
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),说明之前的ThreadLocal对象已经被回收了
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//ThreadLocal对应的key实例不存在,new一个
tab[i] = new Entry(key, value);
int sz = ++size;
//清楚陈旧的Entry(key == null的)
// 如果没有清理陈旧的 Entry 并且数组中的元素大于了阈值,则进行 rehash
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
Эта операция набора отличается от метода, используемого Картой набора для разрешения конфликтов хэшей.Карта набора использует метод цепной адресации, а здесь используется метод открытой адресации (линейное обнаружение). replaceStaleEntry() и cleanSomeSlots() в методе set(), эти два метода могут очищать экземпляр с ключом == null для предотвращения утечек памяти.
getEntry()
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
return getEntryAfterMiss(key, i, e);
}
Из-за использования открытого метода адресации хеш-значение текущего кеу и индекс элемента в массиве не имеют однозначного соответствия, сначала угадываем (хеш-значение ключа), если соответствующий ключ - это тот, который мы ищем элемент, затем вернитесь напрямую, в противном случае вызовите getEntryAfterMiss
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)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
Здесь мы исследовали следующий элемент, зная, что ключ элемента, который мы ищем, и есть то, что мы ищем. Здесь, когда key==null, вызов expungeStaleEntry выгоден для восстановления GC, чтобы предотвратить утечку памяти.
Почему ThreadLocal теряет память
Ключом ThreadLocalMap является экземпляр ThreadLocal, который является слабой ссылкой. Мы знаем, что слабые ссылки полезны для повторного использования сборщиком мусора. и Текущий. Между потоками также существует сильная ссылочная связь. Из-за этой строгой ссылочной связи значение не может быть повторно использовано.Если объект потока не устраняет эту сильную ссылочную связь, может возникнуть OOM. Иногда мы вызываем метод remove() класса ThreadLocalMap для явной обработки.
Суммировать
- ThreadLocal - не решить проблему общих переменных, а также не координировать синхронизацию ниток, он должен содействовать каждому потоку для управления своим собственным состоянием и опорным механизмом.
- Внутри каждого ThreadLocal есть ThreadLocalMap, ключ, который он сохраняет, — это экземпляр ThreadLocal, а его значение — значение копии локальной переменной текущего потока.