多线程访问同一个变量容易引发并发问题,但在每个线程定义同一个变量又不太现实,ThreadLocal的出现就解决了这个问题,Thread为每个线程创建了一个线程变量副本,每个线程操作的数据都是属于自己的,不存在安全问题
实例
1 | public class ThreadLocalTest |
最终输出:
1 | threadOne:threadTwo local |
如上我们明明在set()方法上设置了值,并使用get()获取,按照以往,都是以最后一次set为准,那这里为什么读取的两个线程不一样呢?很自然我们就想到了,它的set()和get()方法具有特殊性。
原理分析
Thread类中有一个threadLocals和inheritableThreadLocals,他们都是ThreadLocalMap类(类似HashMap),默认情况下都为null,什么时候改变,我们看下源代码:
set方法:
1 | public void set(T value) { |
进一步看 getMap(t)方法:
1 | ThreadLocalMap getMap(Thread t) { |
从这段代码,我们就知道,我们真正取到的变量是线程中的变量,createMap也是如此:
1 | void createMap(Thread t, T firstValue) { |
再看get方法:
1 | public T get() { |
发现get方法与set方法差不多,都是从线程变量中以ThreadLocal作为key获取value值。
到这基本就已经了解了ThreadLocal的基本用法了。
细心的同学们肯定发现了一个问题,inheritableThreadLocals还没讲呢,这是什么?
inheritableThreadLocals
问题: ThreadLocal不支持继承性(注意这里的继承不是说父类子类的继承),而是说主线程和子线程中ThreadLocal对各自是不可见的。
还是上面的例子,不过在随后加上下面两行:
1 | System.out.println("main Int:" +localVal.get()); |
程序无论怎么运行,都会打印
1 | main Int:null |
也就是说,子线程的本地变量对父线程不可见,同理父线程变量对子线程不可见。
子线程的本地变量对父线程不可见,可以理解,类似父类子类的关系,但父线程的线程变量对子线程可见还是蛮常见的,InheritableThreadLocal就是为此设计的。
查看源码:InheritableThreadLocal继承ThreadLocal并重写了一下三个方法
1 | protected T childValue(T parentValue) { |
也就是说set(T value)和get()方法是与ThreadLocal一致的,不同的地方在于map的获取与设置,操作的不再是threadLocals这个值,而是inheritableThreadLocals;
然后,它是怎么共享的inheritableThreadLocals的呢?其实原理比较简单,就是在构造线程的时候将当前线程(也就是父线程)的变量赋值到新创建的线程。
1 | public Thread(Runnable target) { |
其实由上面的代码可以看出,父线程的本地变量到子线程可见,是一个深拷贝,也就是说他们的值是不同步的,如果一方修改,那就不一样了。