返回顶部

ThreadLocal

简单介绍

ThreadLocal一般称为线程本地变量,它是一种特殊的线程绑定机制,将变量与线程绑定在一起,为每一个线程维护一个独立的变量副本。通过ThreadLocal可以将对象的可见范围限制在同一个线程内。

跳出误区

需要重点强调的的是,不要拿ThreadLocal和synchronized做类比,因为这种比较压根就是无意义的!sysnchronized是一种互斥同步机制,是为了保证在多线程环境下对于共享资源的正确访问。而ThreadLocal从本质上讲,无非是提供了一个“线程级”变量作用域,它是一种线程封闭(每个线程独享变量)技术,更直白点讲,ThreadLocal可以理解为将对象的作用范围限制在一个线程上下文中,使得变量的作用域为“线程级”。

没有ThreadLocal的时候,一个线程在其生命周期内,可能穿过多个层级,多个方法,如果有个对象需要在此线程周期内多次调用,且是跨层级的(线程内共享),通常的做法是通过参数进行传递;而ThreadLocal将变量绑定在线程上,在一个线程周期内,无论“你身处何地”,只需通过其提供的get方法就可轻松获取到对象。极大地提高了对于“线程级变量”的访问便利性。

举个栗子

在一个AccountService中有一段类似这样的代码:

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

这个AccountService 调用了其他Java类,不知道经过了多少层调用以后,最终来到了一个叫做AccountUtil的地方,在这个类中需要使用Context中的trackerID来做点儿事情:

很明显,这个AccountUtil没有办法拿到Context对象, 怎么办,把Context对象一层层地传递下去,这样AccountUtil不就可以得到了吗?

 

可是这么做改动量太大,涉及到的每一层函数调用都得改动,有很多类都不属于自己的小组管理,还得和别人协调。有些类根本就没有源码,想改都改不了。

另一种方式是,可以把那个set/get TrackerID的方法改成静态(static)的,这样不管跨多少层调用都没有问题!

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

但是这个做又存在一个线程问题: 多线程并发的时候出错,线程1调用了Context.setTrackerID(), 线程2 也调用了Context.setTrackerID(),数据互相覆盖,就会出错。

像这样中情况,需要在某处设置一个值,然后经过重重方法调用,到了另外一处把这个值取出来,又要线程安全,就可以把这个值就放到线程中,让线程携带着这个值到处跑,这样无论在任何地方都可以轻松获得了。每个线程都有一个私家领地,在Thread这个类中有个专门的数据结构,叫做threadLocals的变量,还是个Map类型 ,可以放入TrackerID,然后到任何地方都可以把这个TrackerID给取出来。但是在Thread类中没有对这个变量操作的方法,这个变量不是通过Thread访问的,对他的访问委托给了ThreadLocal。使用时,创建一个ThreadLocal类的实例:

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

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

像‘1234’, ‘5678’这些值都会放到自己所属的线程对象中。使用的时候用get()方法取出:

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

相当于把各自的数据放入到了各自Thread这个对象中去了,每个线程的值自然就区分开了。

threadLocals变量是一个定制的ThreadLocalMap类型,在这个类上的源码有说明:ThreadLocalMap is a customized hash map suitable only for maintaining thread local values. 这个定义的好处是,能存储多个ThreadLocal对象。假设你创建了另外一个threadLocalB:

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

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

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

现在那个Context可以改成,使用ThreadLocal:

 

public class ContextHolder {
    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<XXXContext>,即定义一个Context对象,在线程上下文中都要用到的参数全都放入Context里,再用ContextHolder.get().setParamsA(a)去填值。

 

ThreadLocal这个名字起得有点让人误解, 很容易让人认为是“本地线程”, 其实是用来维护本线程的变量。 

ThreadLocal 并不仅仅是Java中的概念,其他语言例如Python,C#中也有,作用类似。

ThreadLocal在日常工作中用得不多,但是在框架(如Spring)中是个基础性的技术,在事务管理,AOP等领域都能找到。

 

 参考:https://mp.weixin.qq.com/s?src=11&timestamp=1524019551&ver=823&signature=aqqfZFPRQagcu9*eKdZnjKBTD9RZNseyPpLCRENCk4DX7azEuC2WV5JenQicBbigdH68XH*nXrPWhKl6*gxvIytKIOdk6VtlilOPaWow9j5fbmlmRNr8L0iThCPmK7vZ&new=1

https://mp.weixin.qq.com/s?__biz=MzA3MDY0NTMxOQ==&mid=2247484677&idx=1&sn=d623f7d09a4c69c13e0a5c093979838c&chksm=9f38e62da84f6f3be0f810993e5082d0d02a1a2175981a8269e74f1c77e76791dc430a9f7c54&mpshare=1&scene=1&srcid=0418qVYcpCKgnTT7N0JvgnwS&pass_ticket=xalUdsS6K20ALG1p0LmZ9yZTcnHiWd5sX0wTvEL8zhZskEpKGJASD3WU0k3BTrdB#rd

 

posted @ 2018-04-18 10:47  jaden好青年  阅读(200)  评论(0编辑  收藏  举报