车驰夜幕|面试时被问到ThreadLocal别慌,你要的答案都在这里( 二 )

运行结果:
i:6,count:1i:10,count:1i:3,count:1i:0,count:1i:7,count:1i:11,count:1i:9,count:1i:5,count:1i:8,count:1i:1,count:1i:4,count:1i:2,count:1i:13,count:1i:15,count:1i:14,count:1i:19,count:1i:18,count:1i:17,count:1i:12,count:1i:16,count:1realCount:0我们可以看到 , 跟之前的例子运行结果差别很大 , 首先现在count全部都是1 , 之前count有8 , 10 , 11 , 12等很多值 。 其次realCount之前是18 , 现在的realCount却是0 。 为什么会造成这样的差异呢?
2.ThreadLocal的工作原理
先看看示例1中的情况
车驰夜幕|面试时被问到ThreadLocal别慌,你要的答案都在这里我们可以看到多个线程可以同时访问公共资源count , 当某个线程在执行count++的时候 , 可能其他的线程正好同时也执行count++ 。 但由于多个线程变量count的不可见性 , 会导致另外的线程拿到旧的count值+1 , 这样就出现了realCount预计是20 , 但是实际上是18的数据问题 。
再看看示例2中的情况:
车驰夜幕|面试时被问到ThreadLocal别慌,你要的答案都在这里如图所示 , 往大的方向上说 , ThreadLocal会给每一个线程都创建变量的副本 , 保证每个线程访问都是自己的副本 , 相互隔离 。
往小的方向上说 , 每个线程内部都有一个threadLocalMap , 每个threadLocalMap里面都包含了一个entry数组 , 而entry是由threadLocal和数据(这里指的是count)组成的 。 这样一来 , 每个线程都拥有自己专属的变量count 。 示例2中线程1调用calc方法时 , 会先调用的getCount方法 , 由于第一次调用threadLocal.get()返回是空的 , 所以getCount返回值是0 。 这样threadLocal.set(getCount() + 1);就变成了threadLocal.set(0 + 1);它会给线程1中threadLocal的数据值设置成1 。 线程2再调用calc方法 , 同样会先调用getCount方法 , 由于第一次调用threadLocal.get()返回是空的 , 所以getCount返回值也是0 。 这样threadLocal.set(getCount() + 1);会给线程2中threadLocal的数据值也设置成1 。。。。。。 最后每个线程的threadLocal中的数据值都是1 。
还有 , 示例2中打印出来的realCount为什么是0呢?
因为testThreadLocal.getCount()是在主线程中调用的 , 其他的线程改变只会影响自己的副本 , 不会影响原始变量 , count初始值是0 , 所以最后还是0 。
3.ThreadLocal源码解析
在介绍ThreadLocal之前 , 让我们一起先看看Thread类
ThreadLocal.ThreadLocalMap threadLocals = null;可以看到Thread类中定义了一个叫threadLocals的成员变量 , 它的类型是ThreadLocal.ThreadLocalMap 。 很明显ThreadLocalMap是ThreadLocal的内部类 , 验证了我在图中画的内容 , 每个线程都有一个ThreadLocalMap对象 。
我们再重点看看ThreadLocalMap
static class ThreadLocalMap {static class Entry extends WeakReference> {Object value;Entry(ThreadLocal k, Object v) {super(k);value = http://kandian.youth.cn/index/v;}}private static final int INITIAL_CAPACITY = 16;private Entry[] table;private int size = 0;private int threshold; // Default to 0private void setThreshold(int len) {threshold = len * 2 / 3;}........省略}由于该方法太长了 , 我在这里省略了部分内容 。 从以上代码可以看到ThreadLocalMap里面包含了一个叫table的数组 , 它的类型是Entry , Entry是WeakReference(弱引用)的子类 , Entry又包含了 ThreadLocal变量 和Object的value, 其中ThreadLocal变量做为WeakReference的referent 。