На основе OpenJDK 12
Свинец
Эта статья в основном хочет знать два места:
- Экземпляры ThreadLocal кажутся общими для нескольких потоков, но на самом деле они независимы друг от друга Как это реализовано?
- Действительно ли неправильное использование ThreadLocal вызывает OOM? Если да, то в чем причина?
Первый взглядОфициальный API ThreadLocalИнтерпретируется как:
Этот класс предоставляет локальные переменные потока. Эти переменные отличаются от своих обычных аналогов, потому что каждый поток, обращающийся к переменной (через свой метод get или set), имеет свою собственную локальную переменную, которая не зависит от инициализированной копии переменной. поток, который обращается к одному (через свой метод get или set), имеет свою собственную, независимо инициализированную копию переменной.]. Экземпляры ThreadLocal обычно представляют собой закрытые статические поля в классах, которые хотят связать состояние с потоком (например, идентификатор пользователя или идентификатор транзакции).
Вероятно, это означает две вещи:
- ThreadLocal обеспечивает особый способ доступа к переменной: переменная, к которой осуществляется доступ, принадлежит текущему потоку, то есть гарантирует, что переменные каждого потока различны, а переменные, полученные одним и тем же потоком где-либо, непротиворечивы, что называется изоляцией потоков. .
- Если вы хотите использовать ThreadLocal, он обычно определяется как частный статический тип, и, на мой взгляд, лучше определить его как закрытый статический окончательный тип.
Посмотрите на кусок кода:
// 代码来自:
// http://tutorials.jenkov.com/java-concurrency/threadlocal.html
public class ThreadLocalExample {
public static class MyRunnable implements Runnable {
private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
@Override
public void run() {
//注意这里 set的值是run函数的内部变量,如果是MyRunnable的全局变量
//则无法起到线程隔离的作用
threadLocal.set((int) (Math.random() * 100D));
try {
//sleep两秒的作用是让thread2 set操作在thread1的输出之前执行
//如果线程之间是共用threadLocal,则thread2 set操作会覆盖掉thread1的set操作
//从而两者的输出都是thread2 set的值
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(e);
}
System.out.println(threadLocal.get());
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
thread2.start();
thread1.join(); //wait for thread 1 to terminate
thread2.join(); //wait for thread 2 to terminate
}
}
Выходной результат:
thread1 start
thread2 start
38
thread1 join
78
thread2 join
Функция засыпания в течение двух секунд в MyRunnable run состоит в том, чтобы разрешить выполнение операции установки потока 2 до вывода потока 1. Если threadLocal используется совместно между потоками, операция установки потока 2 перезапишет операцию установки потока 1, а вывод обоих установлено значение thread2. , поэтому на выходе должно быть то же значение.
Однако по результатам выполнения кода threadLocals потоков thread1 и thread2 различаются, то есть достигается изоляция потоков.
Как экземпляры ThreadLocal независимы между потоками?
Взгляните на метод set ThreadLocal:
public void set(T value) {
//currentThread是个native方法,会返回对当前执行线程对象的引用。
Thread t = Thread.currentThread();
//getMap 返回线程自身的threadLocals
ThreadLocalMap map = getMap(t);
if (map != null) {
//把value set到线程自身的ThreadLocalMap中了
map.set(this, value);
} else {
//线程自身的ThreadLocalMap未初始化,则先初始化,再set
createMap(t, value);
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//Thread类中
//ThreadLocalMapset的set方法未执行深拷贝,需要注意传递值的类型
ThreadLocal.ThreadLocalMap threadLocals = null;
Как видно из кода, при установке значение будет добавлено к соответствующему потоку в соответствии со ссылкой на объект Thread. Но значением set является все тот же объект, а поскольку передается один и тот же объект, возникает другая проблема: проблема передачи значения параметра и передачи ссылки.
базовый тип
public class ThreadLocalExample {
public static class MyRunnable implements Runnable {
private ThreadLocal<Object> threadLocal = new ThreadLocal<>();
// MyRunnable 全局变量
int random;
@Override
public void run() {
random = (int) (Math.random() * 100D);
threadLocal.set(random);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(e);
}
System.out.println(threadLocal.get());
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
System.out.println("thread1 start");
thread2.start();
System.out.println("thread2 start");
thread1.join(); //wait for thread 1 to terminate
System.out.println("thread1 join");
thread2.join(); //wait for thread 2 to terminate
System.out.println("thread2 join");
}
}
Выходной результат:
thread1 start
thread2 start
//两个值不同
16
thread1 join
75
thread2 join
Из вывода видно, что они изолированы.
тип ссылки
глобальная ссылка
public class ThreadLocalExample {
public static class MyRunnable implements Runnable {
private ThreadLocal<Object> threadLocal = new ThreadLocal<>();
// MyRunnable 全局变量
Obj obj = new Obj();
@Override
public void run() {
obj.value = (int) (Math.random() * 100D);
threadLocal.set(obj);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(e);
}
System.out.println(((Obj) threadLocal.get()).value);
}
class Obj {
int value;
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
System.out.println("thread1 start");
thread2.start();
System.out.println("thread2 start");
thread1.join(); //wait for thread 1 to terminate
System.out.println("thread1 join");
thread2.join(); //wait for thread 2 to terminate
System.out.println("thread2 join");
}
}
Выходной результат:
thread1 start
thread2 start
//两个值相同
36
36
thread1 join
thread2 join
Из результатов вывода, когда значение операции set является глобальной переменной MyRunnable и является ссылочным типом, оно не может играть роль изоляции.
местная ссылка
public class ThreadLocalExample {
public static class MyRunnable implements Runnable {
private ThreadLocal<Object> threadLocal = new ThreadLocal<>();
//Obj obj = new Obj();
@Override
public void run() {
Obj obj = new Obj();
obj.value = (int) (Math.random() * 100D);
threadLocal.set(obj);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(e);
}
System.out.println(((Obj) threadLocal.get()).value);
}
class Obj {
int value;
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
System.out.println("thread1 start");
thread2.start();
System.out.println("thread2 start");
thread1.join(); //wait for thread 1 to terminate
System.out.println("thread1 join");
thread2.join(); //wait for thread 2 to terminate
System.out.println("thread2 join");
}
}
Выходной результат:
thread1 start
thread2 start
//两个值不同
12
19
thread1 join
thread2 join
По результатам вывода локальные ссылки могут быть изолированы друг от друга.
Здесь видно, что ThreadLocal только привязывает установленное значение или ссылку к текущему потоку, но не выполняет соответствующее глубокое копирование, поэтому изоляция потока, которую хочет сделать ThreadLocal, должна быть локальной переменной базового типа или выполняться.
ThreadLocal OOM?
Взгляните на Entry внутри ThreadLocalMap:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Как видно из кода, Entry наследует WeakReference и устанавливает для ThreadLocal значение WeakReference, а для value — сильную ссылку. То есть: когда нет строгой ссылки на переменную ThreadLocal, ее можно переработать.
Тем не менее, все еще есть проблема: ThreadLocalMap поддерживает сопоставление между переменными ThreadLocal и конкретными экземплярами.При повторном использовании переменной ThreadLocal ключ карты становится нулевым, а Entry все еще находится в ThreadLocalMap, поэтому эти не поддающиеся очистке Entry вызовут память утечка.
Собственные методы удаления и установки ThreadLocal не могут справиться с ситуацией, когда сам ThreadLocal имеет значение null, потому что код напрямую принимает свойство threadLocalHashCode ThreadLocal, поэтому, если сам ThreadLocal уже имеет значение null, то вызов remove и set сообщит об исключении нулевого указателя (java.lang .Исключение нулевого указателя).
Поэтому при использовании ThreadLocal не забудьте удалить после использования (метод удаления установит значение Entry и самого Entry равным нулю и очистит его).
Адрес кода JDK 12 ThreadLocal:GitHub.com/jiankun король…
Личный публичный аккаунт WeChat:
Персональный гитхаб:
личный блог: