ThreadLocal
是什么
ThreadLocal是一个线程局部变量,每个线程都可以通过set()和get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离。(每个线程一个该线程副本)
场景
存在一个变量,需要被多个线程访问,但是要求线程之间互不影响,互补干涉。
作用
- 单个线程中该变量可直接访问,不许传参
- 变量在线程间相互隔离,互不影响
和线程局部变量的区别
使用线程局部变量,也可以做到上述两点作用,但是使用的场景不对。如果使用局部变量,那么这个变量只在该线程中,对其他线程来说也需要新建这个变量。
而使用ThrealLocal,它存在于线程的可见区域,然后不同线程使用它的时候会创建一个属于自己线程的变量副本,然后修改的时候是对副本进行修改。
原理
主要方法
set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
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;
}
一个线程有一个threadLocals对应ThreadLoacalMap,不同线程有不同的threadLocalMap,同一个map中存的是不同的threadlocal(key)。不同的ThreadLoacalMap中存有相同的threadLocal(代表变量副本)。相同的threadLocalMap中不同的threadLocal代表不同类的变量。(注:threadLocal是key,副本是value)
测试代码
package threadlocal;
class Variable{
private int value;
public void set(int value){
this.value = value;
}
public int get(){
return value;
}
}
public class ThreadLocals {
private static ThreadLocal threadLocal = new ThreadLocal();
public static void main(String[] args) {
Variable vb = new Variable();
vb.set(2);
new Thread(()->{
try{
threadLocal.set(new Variable());
Variable tt = (Variable) threadLocal.get();
tt.set(10);
threadLocal.set(tt);
try{
Thread.sleep(500);
System.out.println(Thread.currentThread().getName()+":"+((Variable) threadLocal.get()).get());
}catch (Exception e){
e.printStackTrace();
}
}finally {
threadLocal.remove();
}
},"thread1").start();
new Thread(()->{
try{
threadLocal.set(new Variable());
Variable tt = (Variable) threadLocal.get();
tt.set(15);
threadLocal.set(tt);
System.out.println(Thread.currentThread().getName()+":"+((Variable) threadLocal.get()).get());
}finally {
threadLocal.remove();
}
},"thread2").start();
System.out.println("main"+vb.get());
}
}
//输出
//main2
//thread2:15
//thread1:10
entry的key是弱引用
因为key是弱引用,所以当只有key指向ThreadLocal对象的时候,ThreadLocal对象会被回收
内存泄露问题
ThreadLocal被垃圾回收后,在ThreadLocalMap里对应的Entry的键值会变成null,但是Entry是强引用,那么Entry里面存储的Object,并没有办法进行回收。
当使用线程池时,由于线程一致存活,ThreadLocalMap一直存活,进而导致Entry里的Object一致存活,这样就会导致内存泄漏。因为会新建新的entry,原来的就不会被清理掉。
为了避免内存泄露,所以要手动的remove
概念图