представлять
ThreadLocal, как класс в пакете java.lang, начиная с JDK1.2, очень важен в интервью и проектах.Основная цель этого класса —предоставлять локальные переменные потока, так что есть также много мест, где этот класс вызываетсялокальная переменная потока
Буквально этот класс создает локальную переменную для каждого потока, которая на самом деле является ThreadLocal дляПеременнаясуществуетв каждой темесоздаликопировать, так что каждый поток может получить доступ к своему внутреннемукопировать переменную
Обычно, когда дело доходит до многопоточности, это будет рассматриватьсяСинхронизация переменныхОднако ThreadLocal предназначен не для решения проблемы синхронизации многопоточных общих переменных, а для предотвращения влияния переменных каждого потока друг на друга, что эквивалентно манипулированию между потоками.копия переменной, естественно нет необходимости рассматривать проблему многопоточной конкуренции, и естественно нет потери производительности
Как использовать
Давайте сначала посмотрим на эти часто используемые методы
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
Очевидно, что метод get() получает значение копии, принадлежащее потоку, метод set() устанавливает значение, метод remove() удаляет, а initialValue() инициализирует переменную.Давайте сначала посмотрим на следующий пример и испытаем сценарий приложения.
public class Demo {
public static ThreadLocal<Integer> threadLocal = null;
public static void main(String[] args) {
threadLocal = new ThreadLocal<Integer>() {
/**
* 通过重写该方法来初始化ThreadLocal的值
*/
@Override
protected Integer initialValue() {
return 10;
}
};
MyThread t1 = new MyThread(20);
MyThread t2 = new MyThread(30);
t1.start();
// 这里为了描述清晰,省略了try-catch语句块
t1.join();
t2.start();
}
}
В приведенном выше методе мы определяем и инициализируем класс ThreadLocal как 10 (реализованный путем переопределения метода initialValue()), затем запускаем два потока и позволяем потоку t2 дождаться завершения выполнения потока t1 перед выполнением.
Детали класса MyThread следующие:
class MyThread extends Thread {
private int val = 0;
MyThread(int val) {
this.val = val;
}
@Override
public void run() {
System.out.println(Thread.currentThread() + "-BEFORE-" + Demo.threadLocal.get());
Demo.threadLocal.set(val);
System.out.println(Thread.currentThread() + "-AFTER-" + Demo.threadLocal.get());
}
}
Мы получаем текущее значение, вызывая метод get() объекта ThreadLocal, затем устанавливаем новое значение с помощью метода set() (мы устанавливаем разные значения для каждого потока), а затем получаем установленное значение с помощью метода get(). метод
Текущий результат выглядит следующим образом
Ключевым моментом является начальное значение переменной потока t2, отмеченное на рисунке.Хотя мы изменили значение переменной в потоке t1, значение переменной не изменилось в потоке t2, тем самым реализуя уникальную переменную каждого потока .В то же время, если объект ThreadLocal будет повторно использоваться во многих местах, необходимо восстановить локальную переменную до значения по умолчанию, вызвав метод **remove()** перед использованием.
Кто-то может спросить, а нельзя ли для каждого потока определить свои приватные переменные для достижения той же операции? Теоретически, конечно, это осуществимо, но ThreadLocal гораздо удобнее, чем форма приватных переменных, не только ввне потокаВыполняйте юниформ-инициализацию и избегайте установки дополнительных переменных внутри потока.
принцип
Щелкните исходный код ThreadLocal и обнаружите, что значение поля переменной не сохраняется. Похоже, что ThreadLocal не отвечает за сохранение переменной. Мы можем начать только с метода.
Сначала взгляните на метод initial(), ведь в этом методе устанавливается начальное значение по умолчанию для нашей переменной, как показано ниже.
protected T initialValue() {
return null;
}
Нам приходится переписывать этот метод каждый раз, когда мы создаем ThreadLocal, так где именно вызывается этот метод?
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的map
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
// 如果map为空则进行创建
return setInitialValue();
}
Немного бровей, мы узнали здесь, что у нас естьThreadLocalMapобъект, поэтому можно было бы подумать, что это можно сделать, позволивпотоки и переменныеСоздайте таблицу KV, чтобы понять, что у каждого потока есть своя уникальная переменная.
Мы нажимаем на метод getMap(t) и обнаруживаем, что поток t возвращаетсяthreadLocalsсвойство, которое является полем класса Thread:
ThreadLocal.ThreadLocalMap threadLocals = null;
Это атрибут, поддерживаемый классом ThreadLocal. Ни один метод Thread не модифицирует это поле, а сам этот ThreadLocalMap является внутренним классом ThreadLocal, который можно понимать как Map (хотя этот класс не наследует интерфейс Map).
В то же время следует отметить, что объект Entry (пара ключ-значение) в объекте ThreadLocalMap наследует слабую ссылку ThreadLocaMap следующим образом
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
То есть, когда для ThreadLocal задано пустое значение, ключ в записи будет переработан в следующем YGC.
Мы до сих пор не видели метод initialValue(), не волнуйтесь, нажмите на метод setInitialValue(), который вызывается, когда карта обнаруживается пустой в методе get(), как показано ниже.
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(), а метод remove() обычно удаляет пару ключ-значение KV (K — текущий поток), которых больше нет в списке. здесь. Проверьте это, если вам интересно
Меры предосторожности
грязные данные
Из вышеприведенного анализа видно, что ThreadLocal привязан к Thread, и каждому Thread соответствует какое-то значение.Если метод remove() не вызывается после окончания использования, грязные данные будут считаны при следующем повторном использовании (для тот же поток), особенно при использовании пулов потоков (потоки в пулах потоков часто используются повторно)
утечка памяти
Как правило, ThreadLocal устанавливается как статическое поле при его использовании.В это время, когда выполнение потока завершено, V в KV не будет автоматически переработан, поэтому необходимо вовремя вызвать метод remove() для очистки после использования.