用过ThreadLocal吗?它保证线程安全的原理是啥?

197 阅读2分钟

参考:https://mp.weixin.qq.com/s/snIrtOAjFWhOYHrfT9hDqA

遇到问题

最近一个小伙伴把项目中封装的日期工具类用在多线程环境下居然出了问题,来看看怎么回事吧

日期转换的一个工具类

然后将这个工具类用在多线程环境下

结果报异常了

这个异常就不从源码的角度分析了,写一个小Demo,理解了这个小Demo,就理解了原因

一个将数字加10的工具类

然后代码的一部分输出为

什么鬼,不是加10么,怎么都输出了28?这主要是因为线程切换的原因,线程陆续将addNum值设置为0 ,3,7但是都没有执行完(没有执行到return addNum+10这一步)就被切换了,当其中一个线程将addNum值设置为18时,线程陆续开始执行addNum+10这一步,结果都输出了28。SimpleDateFormat的原因和这个类似,那么我们如何解决这个问题呢?

解决方案

解决方案1:每次来都new新的,空间浪费比较大

解决方案2:方法用synchronized修饰,并发上不来

解决方案3:用jdk1.8中的日期格式类DateFormatter,DateTimeFormatter,又不想改项目中用到这个工具类的代码

解决方案4:用ThreadLocal,一个线程一个SimpleDateFormat对象

上面的加10的工具类可以改成如下形式(主要为了演示ThreadLocal的使用)

现在2个工具类都能正常使用了,这是为啥呢?

原理分析

当多个线程同时读写同一共享变量时存在并发问题,如果不共享不就没有并发问题了,一个线程存一个自己的变量,类比原来好几个人玩同一个球,现在一个人一个球,就没有问题了,如何把变量存在线程上呢?其实Thread类内部已经有一个Map容器用来存变量了。它的大概结构如下所示

ThreadLocalMap是一个Map,key是ThreadLocal,value是Object

映射到源码就是如下所示:

ThreadLocalMap是ThreadLocal的一个静态内部类

往ThreadLocalMap里面放值

从ThreadLocalMap里面取值

再回头看一下刚开始的例子,是不是理解的很清楚了呢?