ThreadLocal相当于一个装饰器,装饰一个变量,通常将ThreadLocal实例定义为静态的。
其作用是既使得各线程都可访问到该实例,又使各线程访问到的实际上是本线程私有的变量副本而不用进行同步。
若不使用ThreadLocal,要实现上述两个效果,前者可以通过声明一个静态变量实现,但此时存在线程安全问题;后者可以通过在每个线程中声明一个变量实现,但变量无法在不同线程中共享。
对于多线程资源共享的问题,传统的同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。
前者仅提供一份变量,让不同的线程排队访问;
而后者每一个线程都有自己的一份变量副本(逻辑上的,实际上并没有复制什么副本,因为每个线程有自己的ThreadLocalMap成员,key为ThreadLocal实例的弱引用、value为变量的值,操作ThreadLocal变量实际上是操作线程自己的ThreadLocalMap),所以线程间对该变量的访问也就不存在竞争了,因此可以同时访问而互不影响(感觉实际上只是个语法糖,因为可以为每个线程创建一个变量实现等价效果)。但ThreadLocal变量的这种隔离策略也不是任何情况下都能使用的。如果多个线程并发访问的对象实例只允许或只能创建一个,那只能使用同步机制来访问。
ThreadLocal的典型用途是存储上下文信息,避免在不同代码间来回传递,简化代码。比如在一个Web服务器中,一个线程执行用户的请求,在执行过程中,很多代码都会访问一些共同的信息,比如请求信息、用户身份信息、数据库连接、当前事务等,它们是线程执行过程中的全局信息,如果作为参数在不同代码间传递,代码会很啰嗦,这时,使用ThreadLocal就很方便(设置个static ThreadLocal变量),所以它被用于各种框架如Spring中。
JDK建议将ThreadLocal变量定义成private static
的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。
需要注意的是,线程池中的线程在执行完一个任务,执行下一个任务时,其中的ThreadLocal对象并不会被清空,这会出现与期望不符的情况,需要手动进行一些处理。
真实实践示例:项目中使用到Jedis客户端,然而不想直接使用Jedis,因为使用到的地方就需要处理连接过程,因此打算对之封装一层——将连接处理封装起来,封装成JedisUtil,JedisUtil暴露少量接口供外调用。然而使用过程中遇到一个问题:在JedisUtil中维护一个公共连接时,llen、lpush等可以功能正常使用,然而对于brpop等就有问题了:后者阻塞等待连接返回数据,若与llen等共用一个连接,llen、brpop的返回会交杂在一起,从而出错。因此需要为brpop维护单独的连接来执行该命令,该连接与调用者线程挂钩,这时用ThreadLocal就很方便了。
更多详情: