ThreadLocal
是什么?
简单来讲就是线程局部变量,线程之间操作的变量互不影响各自独立,但如果ThreadLocal
存放的是一个全局共享的引用变量依旧会有线程安全问题,ThreadLocal
只是将共享变量变成私有变量达到线程安全而已
ThreadLocal
的使用
public class ThreadLocalTest01 {
private final static ThreadLocal<Integer> reqId = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) throws InterruptedException {
Thread th1 = new Thread(()->{
reqId.set(1);
System.out.println("th1:"+reqId.get());
});
Thread th2 = new Thread(()->{
//reqId.set(2);
System.out.println("th2:"+reqId.get());
});
th1.start();
th2.start();
}
}
输出结果:
th1:1
th2:0
我们可以看到线程th1
和线程th2
对reqId
变量的修改是相互独立的,互不影响
ThreadLocal
源码分析
get()
源码
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
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);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public class Thread implements Runnable {
***省略其他代码
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
***省略其他代码
}
我们可以看到Thread
对象中维护了一个ThreadLocal.ThreadLocalMap
的变量,其实就是一个类似HashMap
的集合对象,每次get
或者set
都是对这个容器进行操作,使用ThreadLocal
作为key
,我们要存的变量作为value
。
其实就一句话,因为每个线程对象里面都有一个
ThreadLocalMap
对象,使用ThreadLocal
作为key
进行存储或者查询,这也就是线程局部变量的由来,操作的是线程对象里面的属性
ThreadLocal
在线程池中的使用注意事项
我们将前面的那个例子换成使用线程池去执行看看效果
public class ThreadLocalTest02 {
private final static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
private final static ExecutorService executor = Executors.newFixedThreadPool(3);
public static void main(String[] args) throws InterruptedException {
while (true){
executor.submit(()->{
threadLocal.set(1); //01
System.out.println("t1 get:"+threadLocal.get());
//threadLocal.remove(); //03
});
Thread.sleep(100);
executor.submit(() -> {
//threadLocal.set(1);
System.out.println("t2 get:" + threadLocal.get());//02
});
}
}
}
输出结果:
t1 get:1
t2 get:null
t1 get:1
t2 get:1
t1 get:1
t1 get:1
t2 get:1
t2 get:1
我们会发现使用线程池后,线程t2会读到线程t1的局部变量。原因很简单那是因为线程池中的线程会复用,我们提交的两个任务,在对于线程池而言很可能会被同一个线程执行,假设线程池中的线程t1执行完后01行代码后,这时在处理新任务02时,就会出现这种错误现象。
解决办法:每次set()
完后记得使用remove()
清除局部变量即03行注释去掉