Is your ThreadLocal thread safe

Many friends must be familiar with ThreadLocal. ThreadLocal is called thread local variable, that is, ThreadLocal creates a copy of the variable in each thread, and each thread can access its own internal copy variable. So, is it thread safe to use ThreadLocal? Without much to say, let's draw a conclusion first:

In order to demonstrate the wrong way of use, first look at the following code (although little friends don't write code like this ^ ^ ^):

static class Container {
    int num;
}
public void main(String[] args) throws InterruptedException {
    ThreadLocal<Container> tl = new ThreadLocal<>();
    tl.set(Container()); // 先set下ThreadLocal

    Container container = tl.get();
    Runnable task = () -> {
        for (int i = 0; i < 10000; i++) {
            container.num++;
        }
    };

    Thread t1 Thread(task);
    Thread t2 Thread(task);

    t1.start();
    t2.start();
    t1.join();
    t2.join();

    System.out.println(tl.get().num);
}

Combined with the code, we know that we are executing threadlcoal After get gets the copy of the thread variable, do not let other threads access it. Otherwise, multiple threads operate on the same variable, which may cause thread safety problems.

In addition to the ThreadLocal thread security issues discussed above, ThreadLocal may have a memory leak if it is not used properly. ThreadLocal variable is saved in thread Threadlocals (threadlocalmap type) is saved as an entry type, where entry.key (that is, the object actually pointed to by the weak reference referent) is the ThreadLocal variable, which is of weak type; entry.value is the value of the actual set.

// Entry,里面保存在ThreadLocal变量,也就是key,是弱引用
Entry extends WeakReference<ThreadLocal?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal> k, Object v) {
        super(k);
        value = v;
    }
}

Although entry Referent is a weak type and points to the ThreadLocal variable, but if the ThreadLocal variable reference itself is not set to null, the entry here Referent objects are not released. For example, our common definition method:

类似于静态变量和对象属性这种引用,如果不将tls或tli设置为null,那么ThreadLocal变量无法释放(这不是废话么,人家可是强引用呀),此时的Entry.referent弱类型没啥卵用;只有在tls或tli为null时,Entry.referent弱类型就起作用了,在第一次GC时就会将Entry.referent弱类型指向的对象回收。

如果Entry.referent弱类型指向的对象回收了(没调用ThreadLocal.remove操作),Entry.value对象还在,并且Entry.value可是强引用的,此时就发生了内存泄露。这也就是ThreadLocal使用不当(没调用ThreadLocal.remove)时产生的内存泄漏问题。不过,伴随着其他ThreadLocal对象的set/get/remove的进行,会清除一部分Entry.referent为null但是Entry.value不为null的对象的,也就是修复内存泄露问题,注意,这个只是清除部分这样的Entry,并不能保证一次就能清除全部这样的Entry,所以还是要遵循ThreadLocal.set,用完之后就remove。

讨论完了ThreadLocal的潜在问题之后,你是不是意犹未尽,想深入了解下ThreadLocal实现原理?OK,那就搬起小板凳,一起唠唠吧~

ps:如果小伙伴对ThreadLocal原理已经熟悉了,那么恭喜你,后面的内容可以不看了~

ThreadLocal实现原理

ThreadLocal变量主要有get/set/remove三个操作,理解了这三个操作流程,基本上就理解了ThreadLocal实现原理。

get

get流程如下:

  1. 获取当前线程的threadLocals(map结构),从threadLocals中获取当前ThreadLocal变量对应的ThreadLocalMap.Entry(pair类型,包含了当前ThreadLocal变量及其对应的value),非空直接返回对应的value
  2. 为空时使用默认值(默认为null)构造ThreadLocalMap.Entry,放到当前线程的threadLocals中,下次再get时直接返回ThreadLocalMap.Entry对应的value即可
注意:线程的threadLocals是一个基于开放定址法实现的map结构。

set

  • set操作就是将ThreadLocal变量的值put到当前线程的threadLocals中,ThreadLocal变量及其对应的值会构造成一个ThreadLocalMap.Entry放到threadLocals中。
  • 因为线程的threadLocals是一个基于开放定址法实现的map结构,所以在出现hash冲突后会继续寻找下一个空位进行set操作。
  • 因为是基于开放定址法,如果map中元素过多,会影响get和put性能,所以需要扩容,map的数组结构默认大小为INITIAL_CAPACITY = 16,默认扩容阈值为threshold = INITIAL_CAPACITY * 2 / 3,扩容时按照成倍扩容。
remove


小结

从ThreadLocal的get/set操作流程来看,ThreadLocal的value 是 Lazy Init(延迟初始化的)。ThreadLocal为什么是延迟初始化,这个问题应该是容易理解的,原因是:在没有具体业务场景前提下,这样的做法避免内存浪费。

ThreadLocal变量默认放在基于开放定址法实现的map结构中,这种结构在hash冲突时会造成多次get/set操作,理论上可以通过记录ThreadLocal变量set时的位置,这样下次直接通过该位置获取对应value即可,可以参考netty的FastThreadLocal,它的实现思路就是这样的,提高了set/get的效率。

 

最后来一张ThreadLocal的整体图:

参考资料:

1、https://luoxn28.github.io/2019/04/27/ni-de-threadlocal-yi-ding-xian-cheng-an-quan-ma/



总结

以上是编程之家为你收集整理的你的ThreadLocal线程安全么全部内容,希望文章能够帮你解决你的ThreadLocal线程安全么所遇到的程序开发问题。

如果觉得编程之家网站内容还不错,欢迎将编程之家网站推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。

喜欢与人分享编程技术与工作经验,欢迎加入编程之家官方交流群!

编程之家官方1群

编程之家官方2群



The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>