一个故事讲明白线程的私家领地:ThreadLocal

 

原创 2018-01-31 老刘 码农翻身安全

张大胖上午遇到了一个棘手的问题,他在一个AccountService中写了一段相似这样的代码:数据结构

Context ctx = new Context();
ctx.setTrackerID(.....)

 

而后这个AccountService 调用了其余Java类,不知道通过了多少层调用之后,最终来到了一个叫作AccountUtil的地方,在这个类中须要使用Context中的trackerID来作点儿事情:多线程

很明显,这个AccountUtil没有办法拿到Context对象, 怎么办?并发

 

张大胖想到,要不把Context对象一层层地传递下去,这样AccountUtil不就能够获得了吗?框架

 

 

但是这么作改动量太大!涉及到的每一层函数调用都得改动,有不少类都不属于本身的小组管理,还得和别人协调。 函数

更要命的是有些类根本就没有源码,想改都改不了。ui

这也难不住我,张大胖想:能够把那个set/get TrackerID的方法改为静态(static)的,这样无论跨多少层调用都没有问题!spa

 

public class Context{
    public static String getTrackerID(){
        ......
    }
    public static void setTrackerID(String id){
        ......
    }
}

 

 

这样就不用一层层地传递了,Perfect!线程

张大胖得意洋洋地把代码提交给Bill作Review。 3d

Bill看了一眼就指出了致命的问题: 多线程并发的时候出错!

张大胖巴不得找个地缝钻进去:又栽在多线程上面了,此次犯的仍是低级错误!

线程1调用了Context.setTrackerID(), 线程2 也调用了Context.setTrackerID(),数据互相覆盖,不出乱子才怪。

张大胖感慨地说:“像我这样中状况,须要在某处设置一个值,而后通过重重方法调用,到了另一处把这个值取出来,又要线程安全,实在是很差办啊, 对了,我能不能把这个值就放到线程中? 让线程携带着这个值处处跑,这样我不管在任何地方均可以轻松得到了!”

Bill说:“有啊,每一个线程都有一个私家领地! 在Thread这个类中有个专门的数据结构,你能够放入你的TrackerID,而后到任何地方均可以把这个TrackerID给取出来。”

“这么好? ” 

张大胖打开JDK中的Thread类,仔细查看,果真在其中有个叫作threadLocals的变量,仍是个Map类型 , 可是在Thread类中却没有对这个变量操做的方法。 

看到张大胖的疑惑,Bill说:“也许你注意到了,这个变量不是经过Thread的访问的,对他的访问委托给了ThreadLocal这个类。

“那我怎么使用它?”

“很是简单, 你能够轻松建立一个ThreadLocal类的实例:

ThreadLocal<String> threadLocalA= new ThreadLocal<String>();

线程1: threadLocalA.set("1234");
线程2: threadLocalA.set("5678");

 

像‘1234’, ‘5678’这些值都会放到本身所属的线程对象中。”

“等你使用的时候,能够这么办:”

线程1: threadLocalA.get()  --> "1234"
线程2: threadLocalA.get() --> "5678"

 

“明白了,至关于把各自的数据放入到了各自Thread这个对象中去了,每一个线程的值天然就区分开了。 但是我不明白的是为何那个数据结构是个map 呢?”

“你想一想,假设你建立了另一个threadLocalB:”

ThreadLocal<Integer> threadLocalB = new ThreadLocal<Integer>();

线程1: threadLocalB.set(30);
线程2: threadLocalB.set(40);

 

那线程对象的Map就起到做用了:

“明白了,这个私家领地还真是好用,我如今就把我那个Context给改了,让它使用ThreadLocal:”

public class Context {
    private static final ThreadLocal<String> mThreadLocal 
        = new ThreadLocal<String>();

    public static void setTrackerID(String id) {
        mThreadLocal.set(id); 
    }   
    public static String getTrackerID() {
        return mThreadLocal.get();
    }   

}

 

小结:

ThreadLocal这个名字起得有点让人误解, 很容易让人认为是“本地线程”, 实际上是用来维护本线程的变量。 对照着上面的原理讲解,我想你们能够自行去看ThreadLocal的源码,轻松理解。

ThreadLocal 并不只仅是Java中的概念,其余语言例如Python,C#中也有,做用相似。

ThreadLocal在平常工做中用得很少,可是在框架(如Spring)中是个基础性的技术,在事务管理,AOP等领域都能找到。

(完)