Java 并发编程 ThreadLocal

12 篇文章 0 订阅
12 篇文章 0 订阅

​ThreadLocal源码分析  线程变量透传  如何避免脏数据  内存溢出

2014年拍摄于甘南藏族自治区桑科草原,喜欢阴天

微信公众号

王皓的GitHub:https://github.com/TenaciousDWang

 

今天这回说一下ThreadLocal这个类。

 

ThreadLocal为变量在每个线程中都创建了一个独立副本,那么每个线程可以访问自己内部的副本变量,其他线程不能访问,所以不会产生线程安全问题,但是ThreadLocal无法解决共享变量更新问题,依然需要线程同步。

 

这里引用《码出高效》里面CS的例子进行改造来说一下ThreadLocal的使用方法和场景。

 

在CS中每人对应三个数值,子弹数,杀敌数,自己的血量,初始值分别为1500,0,10.假设每个人属于一个线程,这三个初始值写在什么地方呢?每一个线程写死的话,如果同意修改子弹数量了呢?如果共享,由于每个人,每个线程对数据操作不同会导致数据不准确,这时我们就可以使用ThreadLocal对象,设置为共享变量,统一设置初始值,但是每个线程都有一个副本是独立的,对其他线程不可见的,《码出高效》中对于ThreadLocal的名称解释为CopyValueIntoEveryThread就显得非常贴切。

 

 

首先我们看一下ThreadLocal创建时通常都是使用private static 来修饰的。

 

代码示例中并没有对ThreadLocal进行set操作,而是重写了initialValue方法返回一个共享变量,我们来看一下ThreadLocal的get操作的源码。

 

 

源码中,每一个Thread都包含一个ThreadLocalMap,ThreadLocal的get方法实际上是从这个Map中获取Entry,再从Entry中获取当前副本的值,如果无法获取,调用setInitialValue方法。

 

 

其中如果当前线程没有ThreadLocalMap就创建,有的话会set进一个初始键ThreadLocal<T>对应值为initialValue方法返回,这是一个保护方法。

 

 

这里我们之前重写了initialValue方法不返回null,而是返回了我们定义的共享变量。

 

接下来我们在线程的run方法内写入ThreadLocal变量减去随机的int值,并打印每一个线程对应的三个值的结果。这里为了结果容易分辨,我加了一个锁,让其按顺序打印。

 

 

我们可以看一下打印的结果,每一个线程的副本变量都只对自己可见,对其他线程不可见,打印的都是线程自己对自己副本变量操作的结果,线程间不会互相干扰,可以看出是线程安全的。

 

以上是每个线程对自己的变量副本进行操作,如果是多线程对共享变量进行更新则ThreadLocal无法解决,我们依旧需要线程同步,例子如下,将变量改为可变对象StringBuilder,注意其兄弟StringBuffer是线程安全的。

 

 

此时sb这个ThreadLocal是一个StringBuilder线程不安全的可变对象,我们起十个线程来对其添加-i,我们来看一下运行结果。

 

 

我们看到开始打印为正常顺序,到后面时,已经变为乱序,因此当有多线程引用共享变量时,依旧需要线程同步的,ThreadLocal并不能解决共享变量多线程操作的安全问题。

 

接下来看一下ThreadLocal使用中需要注意的脏数据,内存泄漏问题。

 

A、脏数据,我们知道线程池属于复用线程,也就是说线程中的ThreadLocal也会被复用,如果下一个线程没有set初始值,那么就有可能get到上一下线程用过的信息,或者上一个线程没有进行ThreadLocal的remove操作。

 

B、内存泄漏。

 

 

源码注释中写道,ThreadLocal对象通常最为私有静态变量使用,其生命周期不会随着线程结束而结束,所以必须及时调用remove方法来清理,避免内存泄漏。

 

总结上述线程使用ThreadLocal三个重要方法需要注意的问题:

 

1、set:如果没有set操作ThreadLocal,容易引起脏数据问题。

 

2、get:始终没有get操作的ThreadLocal是没有意义的。

 

3、remove:如果没有remove操作,容易引起内存泄漏问题。

 

最后说一个ThreadLocal子类InheritableThreadLocal用于子线程共享父线程本地变量的功能,也就是线程透传功能。

 

举一个例子,先来看一下基本使用方法与场景。

 

 

main为父线程,创建InheritableThreadLocal,然后在父线程中创建子线程threadTest,我们看一下运行结果。

 

 

可以获得父线程内变量的值。为什么能获取到呢,我们来看一下InheritableThreadLocal的源码来分析一下。

 

 

InheritableThreadLocal继承自ThreadLocal,并且重写了父类的方法:createMap,getMap,childValue。createMap中使用了Thread中的inheritableThreadLocals我们来看一下它会被怎么赋值。

 

 

会把parent也就是父线程,使用createInheritedMap方法来把父线程的inheritableThreadLocals中不是null的线程变量都copy过来。

 

 

所以子线程可以获取到父线程的线程内变量。但是InheritableThreadLocal其实在使用线程池时也存在脏数据的问题,目前的解决方案是使用alibaba的开源项目transmittable-thread-local来解决这个问题。大家可以上github上自行查看使用说明,写的很详细。

 

最后说一个是关于SimpleDateFormat,是一个线程不安全的类,定义为static会有同步风险,多线程共享时会产生错误。《码出高效》推荐使用ThreadLocal来解决。

 

 

好了,这就是今天要说Java并发编程ThreadLocal,喜欢请关注~

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值