1、回顾ThreadLocal
之前写过一篇ThreadLocal简单使用的日志,里面展示了一个例子,以及get()和set()方法的源码。ThreadLocal是不同于锁的方式解决了多线程访问临界资源的安全性问题,且某些场景下效率更高。最近也在读ThreadLocal的源码,发现很多知识点上一篇日志没有兼顾到(例如令我印象深刻的是线程中的ThreadLocalMap中维护的Entry[]数组是环形结构,数组的扩容阈值和用到了哈希函数和线性探测方式,都是当初学习数据结构与算法时满满的回忆哈哈),所以现在决定再写一篇自己对ThreadLocal学习总结的日志。
ThreadLocal的作用是把数据副本放入到该ThreadLocal实例所在的线程的Map中,Map中的key对应ThreadLocal的实例对象,value则对应我们放进去的数据。因为Map只属于当前线程,它是Thread类内部的一个实例变量,所以,只有当前线程可以访问到这个Map,对这个Map里面的数据做修改,所以可以保证多线程修改同一数据的安全性问题,通过隔离的方法,即把数据副本放入到各自线程的Map中去,各个线程修改自己的副本数据。
使用ThreadLocal还有一个很大的好处是,提高了效率。为了保证数据的安全性,我们很自然地会想到加锁,在对临界资源数据做修改前先加锁,修改操作完成后再释放锁,这种做法的确保护了数据的安全,但效率不高,因为一旦一个线程在修改临界资源时加锁了,那么其他想要访问这个临界资源的线程便不得不停下来,等待锁的释放,试想假如有大量线程因为等待锁而被阻塞,肯定导致整体效率不高。使用ThreadLocal则可以解决效率问题,正如上面所说,把数据副本放入到自己线程的Map中修改,线程之间互不干扰,也就不会有互相等待的问题了。
2、存储数据结构
2.1 ThreadLocalMap中的成员变量
// ThreadLocalMap的初始大小capacity
private static final int INITIAL_CAPACITY = 16;
// Entry数组
private Entry[] table;
// Entry数组的大小
private int size = 0;
// Entry数组下一次扩容的阈值
private int threshold;
可以看到,在线程的ThreadLocalMap中,有一个Entry[]数组,用来存放数据副本,这个Entry[]数组就是我们说的线程里的Map。前面说到,每一个Thread都有各自的Map,ThreadLocal类只是把数据放入到所在线程的Map中,然后Map中的key对应ThreadLocal类的实例,value则对应存储数据。但是注意,这个Map并不是并不是我们java.util.map包中的map,它只是形式上像Map,因为有key和value。那么这个key和value是怎么对应的呢?来看看这个存储在线程中的ThreadLocalMap,它的存储结点Entry是怎样定义的: