public class ThreadLocalDemo {static ThreadLocal<Integer> local = new ThreadLocal<Integer>(){@Overrideprotected Integer initialValue() {return 0;}};public static void main(String[] args) {Thread[] threads = new Thread[5];for (int i = 0; i < 5; i++) {threads[i] = new Thread(()->{Integer num = local.get();local.set(num+ 5);System.out.println(Thread.currentThread().getName()+ \"-\" + num);});}for (int i = 0; i < 5; i++) {threads[i].start();}}}
代码输出:
Thread-0-0Thread-3-0Thread-4-0Thread-2-0Thread-1-0
解释:
各个线程获取的值不会受到其他线程修改值的影响
原理大致如图:
对流程的说明:
对于新开启的一个线程Thread,内部有一个ThreadLocalMap对象(类似于hash map)存储供当前线程使用的自定义的Object value。
该map的key为ThreadLocal实例,值为用户自定义使用的值。
下面就解析一下ThreadLocal的set方法
这个方法分为两步走:
- 创建新的ThreadLocalMap
- 将当前的ThreadLocal放入到已有的ThreadLocalMap中
看第一步:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}
就是ThreadLocalMap的构造函数啦, 给table赋值一个 初始化大小的Entry数组,再根据threadLocal的hashcode和长度进行&比较获取table中的下标,然后插入新的Entry值。
看第二步啦:
这有三个if,一个for:
for: 遍历ThreadLocalMap中的Entry数组,找到不为空的进行判断
- 如果是同一个坑的key,直接覆盖value
- 如果k为空,也就是threadLocal为空但是value还存在的情况,
注释:当一个ThreadLocal失去强引用,生命周期只能存活到下次gc前,此时ThreadLocalMap中就会出现key为null的Entry,当前线程无法结束,这些key为null的Entry的value就会一直存在一条强引用链,造成内存泄露。
for 循环遍历结束后,将在对应的table位置新建一个entry存放新值。
并处理那些标记为需要清除的slot entry ,并进行rehash