ThreadLocal解析与最佳实践

概述

用于同一个线程内的方法要共享某些变量或状态的时候,提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度

源码解读

源码的阅读主要集中在几个关键方法

构造函数

  /**
     * Creates a thread local variable.
     */
    public ThreadLocal() {
    }

可以看出,默认的构造函数什么都没有干,但如果需要设置初始值怎么办

initialValue()

    protected T initialValue() {
        return null;
    }

使用者可以通过继承ThreadLocal覆盖该方法来设置初始值,该值在第一次调用get()方法时被调用,该方法在整个ThreadLocal的生命周期中应该只对调用一次,除非用户显示地调用了remove(),然后又调用get()时会再次调用initialValue

get()

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

get方法中可以看出先得到当前线程的threadlocalmap,如果不存在该map(首次调用get()),则调用setInitialValue(),如果存在则得到当前Key对应的值

setInitialValue()

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

先调用intialValue得到初始值,然后得到该线程对应的ThreadLocalMap,然后在Map中set初始值,如果没有ThreadLocalMap则创建,并设置当前TheadLocal初始值.从上可以看出,初始化的时候可能做两件事 1、已有map 则将ThreadLocal作为key,initialValue为value放入到map中

2、没有map 新建一个ThreadLocalMap,并将<key,value>放入其中

createMap()

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

set(T value)

   public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

和setInitialValue方法类似

remove()

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

比较重要的一个方法,将当前的threadlocal变量从map中移除。

tips

比较重要的一点是,ThreadLocal,Thread,ThreadLocalMap的设计 目前的设计是Thread中有ThreadLocalMap,Map中以ThreadLocal为key,这种设计非常的清晰,由于在ThreadLocalMap中ThreadLocal是以WeakReference的形式存在的,所以其引用链或如下所示,也会产生GC疑问:ThreadLocal被回收,但是map中的entry一直不能回收的问题。 所以引出了最佳实践问题

threadlocal的引用链

ThreadLocal引用链.png

最佳实践

最佳实践的方法参见google guava eventbus中对于ThreadLocal的使用

    private final ThreadLocal<Boolean> dispatching;
    
    this.dispatching = new ThreadLocal() {
                protected Boolean initialValue() {
                    return Boolean.valueOf(false);
                }
            }
    if(!((Boolean)this.dispatching.get()).booleanValue()) {
                this.dispatching.set(Boolean.valueOf(true));

                Dispatcher.PerThreadQueuedDispatcher.Event nextEvent;
                try {
                    while((nextEvent = (Dispatcher.PerThreadQueuedDispatcher.Event)queueForThread.poll()) != null) {
                        while(nextEvent.subscribers.hasNext()) {
                            ((Subscriber)nextEvent.subscribers.next()).dispatchEvent(nextEvent.event);
                        }
                    }
                } finally {
                    this.dispatching.remove();
                    this.queue.remove();
                }
            }
  • 采用匿名内部类赋初始值
  • 显式调用get()、set()
  • 在不用的时候显式地remove()掉
  • 对于显示的remove特别重要,因为这样可以避免entry不被GC的情况 如果为了避免ThreadLocal被GC,可以加强ThreadLocal的引用,将其声明成private static

致谢

本文参考了解密ThreadLocal,引用了文中的图片,感谢作者,侵删。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏函数式编程语言及工具

Scalaz(36)- Free :实践-Free In Action - 实用体验

在上面几期讨论中我们连续介绍了Free Monad。因为FP是纯函数编程,也既是纯函数的组合集成,要求把纯代码和副作用代码可以分离开来。Free Monad...

19650
来自专栏haifeiWu与他朋友们的专栏

死磕Java之聊聊ThreadLocal源码(基于JDK1.8)

记得在一次面试中被问到ThreadLocal,答得马马虎虎,所以打算研究一下ThreadLocal的源码

48720
来自专栏web前端

Button按钮--inject与provide

inject 和 provider 是vue中的组合选项,需要一起使用。目的是允许一个祖先组件向其所有子孙后代注入依赖(简单地说就是祖先组件向子孙后代传值的一种...

25210
来自专栏haifeiWu与他朋友们的专栏

Kotlin:Android开发技巧

Kotlin作为Android开发第一语言,然而身边做Android的大多还是使用java。Android转到Kotlin的趋势是必然的,公司隔壁部门已经全部使...

15530
来自专栏服务端技术杂谈

dubbo源码学习笔记----Provider和Consumer

provider <!-- provider's application name, used for tracing dependency relat...

27560
来自专栏算法修养

LeetCode 131 Palindrome Partitioning

思路是,先将所有的回文子串都找出来,记录下左右端点。 然后DFS这些子串就可以了。

9210
来自专栏Ldpe2G的个人博客

Graphviz4S ---- 在Scala中使用DOT语言绘图的开源工具

22060
来自专栏峰会SaaS大佬云集

关于 hibernate 双向一对多的数据保存

    private Set<Enroll> enrolls=new HashSet<Enroll>();

14020
来自专栏DOTNET

.NET MongoDB Driver 2.2使用示例

说明:mongoDBService是对各种常用操作的封装 public class MongoDBService { #region 变量 ...

364100
来自专栏aCloudDeveloper

百炼OJ 2744找相同子串

看到这个题,我首先想的是怎么样找出每一个输入的字符串中相同的子串然后将其保存起来,因为数组是动态输入的,每输入一次都要保存好几次,这个过程势必会很麻烦,突然就一...

19860

扫码关注云+社区

领取腾讯云代金券