一文带你深入了解ThreadLocal

ThreadLocal用途

ThreadLocal主要用于多线程之间隔离变量和线程内共享数据。

ThreadLocal保证线程安全,因为其特殊的结构,不存在多线程之间共享数据,也不会存在线程同步锁带来的性能消耗。

ThreadLocal用法

  private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
private static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
integerThreadLocal.set(finalI);
stringThreadLocal.set(finalI % 2 == 0 ? "偶数" : "奇数");
System.out.println(Thread.currentThread().getName() +": "+ integerThreadLocal.get());
System.out.println(Thread.currentThread().getName() +": "+ stringThreadLocal.get());
}, "thread"+i).start();
}
}
/**
thread0: 0
thread4: 4
thread4: 偶数
thread3: 3
thread3: 奇数
thread7: 7
thread2: 2
thread2: 偶数
thread1: 1
thread8: 8
thread8: 偶数
thread9: 9
thread9: 奇数
thread7: 奇数
thread6: 6
thread6: 偶数
thread5: 5
thread5: 奇数
thread0: 偶数
thread1: 奇数

Process finished with exit code 0
*/

上面的代码中,创建了10个线程每个线程保存两份ThreadLocal数据,ThreadLocal<Integer>中保存的是线程在循环中所处的位置,ThreadLocal<String>中保存这个数字是奇数还是偶数,线程的名字为“thread”加上循环中所处的位置。
多个线程操作同一个ThreadLocal,但是ThreadLocal在每个线程中都留有一份副本,因此每个线程操作的数据都不同,获取出来的数据也不同。
下面介绍一下ThreadLocal的原理,看一下ThreadLocal怎样保证线程安全和线程内数据共享的。

ThreadLocal原理

ThreadLocal在每个线程内都留有一份副本,存放在Thread类中的ThreadLocalMap中。

ThreadLocalMap threadLocals = null;

ThreadLocalMap是一个key、value的哈希表,key存放的是ThreadLocal对象,value是要保存的值。

每次访问ThreadLocal类的get()方法,会首先获取当前Thread对象,并拿到Thread对象的ThreadLocalMap。根据当前ThreadLocal对象,在ThreadLocalMap中获取相应的值。

下面的代码反映了这个过程。

  public T get() {
Thread var1 = Thread.currentThread();
ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);
if (var2 != null) {
ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
if (var3 != null) {
Object var4 = var3.value;
return var4;
}
}

return this.setInitialValue();
}

ThreadLocal.ThreadLocalMap getMap(Thread var1) {
return var1.threadLocals;
}

通过上面的了解,我绘制了ThreadLocal,Thread,ThreadLocalMap之间的关系图。

一文带你深入了解ThreadLocal

ThreadLocalMap

上面的原理中,我们知道了ThreadLocalMap存放于Thread中,每个线程获取自己的ThreadLocalMap,不会存在竞争,也不会有线程安全问题。

下面我们再详细介绍一下ThreadLocalMap,看到名字就知道这是一个Map数据结构。ThreadLocalMap使用的是线性探测再散列法解决冲突。

举个例子介绍一下线性探测再散列法。

开辟一个大小为8的数组,下为0~7。

假设哈希函数为:x % 8,其中x为要保存的值。

假设要保存的第一个值是1,1%8=1,所以1这个数据存放在数组下标为1的位置。

要保存第二个值是2,2%8=2,所以2这个数据存放在数组下标为2的位置。

如果要保存的值是9,9%8=1,此时数组下标1的位置已经存在一个数据,并且下标1的数据是1,和9不相同,因此要想保存数据,就需要找下一个下标,也就是2。

不巧的是下标2的位置上也有数据,并且下标2中的数据2和9不同,就需要再找下一个空的位置,找到下标3,此时3里面不存在数据,9就放在了数组中下标为3的位置中。

线性探测再散列法就是不断地向下查找,直到找到数据或者存放数据。最差的性能是O(n),也就是全部查找对比一遍。

弱引用在ThreadLocal中的应用

ThreadLocalMap查找时,定位到数据后,会对比Entry的key是否相同(也就是ThreadLocal是否相同)。Entry的结构如下:

一文带你深入了解ThreadLocal

Entry是ThreadLocalMap中数组的一个对象,继承了WeakReference。其中key为弱引用,value为强引用。

    static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;

Entry(ThreadLocal<?> var1, Object var2) {
super(var1);
this.value = var2;
}
}

当ThreadLocal已经被回收时(线程销毁),key不存在强引用可达,并且key是弱引用,在发生GC时就会被回收。

此时ThreadLocalMap中的key就设置为了null,但是value是强引用的


原文始发于微信公众号(Java不惑):一文带你深入了解ThreadLocal

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/24515.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!