简单介绍
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类的实例:
ThreadLocalthreadLocalA= new ThreadLocal ();线程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:
ThreadLocalthreadLocalB = new ThreadLocal ();线程1: threadLocalB.set(30);线程2: threadLocalB.set(40);
那线程对象的Map就起到作用了:
现在那个Context可以改成,使用ThreadLocal:
public class ContextHolder { private static final ThreadLocalmThreadLocal = new ThreadLocal (); 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等领域都能找到。
参考: