转载

ThreadLocal可以解决并发问题吗

点击上方 "IT牧场" ,选择 "设为星标"

技术干货每日送达!

到底什么是线程的不安全?为什么会存在线程的不安全?线程的不安全其实就是多个线程并发的去操作同一共享变量没用做同步所产生意料之外的结果。那是如何体现出来的呢?我们看下面的一个非常经典的例子:两个操作员同时操作同一个银行账户,A操作员存钱,100B操作员取钱50。我们看一下流程。

ThreadLocal可以解决并发问题吗

两个操作员同时处理,没用做同步这个时候我们发现银行账户最终余额剩余950元,在我们想的最终结果银行账户应该剩余1000+100-50=1050元,在执行过程中我们没有加锁,最终导致了运行结果偏离预期。那么如何解决的?一般的解决措施就是加锁,加同步锁所以这就需要使用者一定要知道锁是什么。我们来看一下加锁之后的效果是不是我们所预期的。

ThreadLocal可以解决并发问题吗

在添加同步锁后我们可以看到,A操作员和B操作员同时去操作账户,但是A先抢占到资源,所以B就只能等待A操作员释放锁才能去操作银行账户,那么最终结果是我们所预期的吗?答案是的。

同步的话一般都是加锁,如果现在我想创建多个线程每个线程都是访问的自己的变量呢?各个线程之间毫无关联?

答案是有的。

ThreadLocal问题

ThreadLocal 是JDK提供的,它提供了线程本地变量。什么是线程本地变量呢?其实就是你创建了一个 Threadlocal 变量,每个访问 Threadlocal 变量的线程都有一个本地副本。我们看下面的图:

ThreadLocal可以解决并发问题吗 从上面看出你创建一个 ThreadLocal 变量,每个访问该的线程都会复制到自己的本地,所以线程操作的都是本地的副本,这也就是说每个线程都是操作的自己本地的变量,那就完美的避免了线程安全的问题。

在这里还有一个问题。我在写这篇文章的时候看过很多文章,总的来说就是 ThreadLocal 就是为了解决多线程并发问题而提供的一种方法,还有一种解释就是 ThreadLocal 的最终目的就是为了解决多线程访问共享资源所产生的。真的对吗? ThreadLocal 并没有共享那么从何而来的同步呢?

自己的想法

在看了 Java并发编程之美 后我所理解的 Threadlocal 提供了线程本地变量的副本,每个线程实际操作的是自己本地的变量副本,也就是说该变量副本只能当前线程访问,就不存在多个线程共享的问题,从 Threadlocal 名字我们也能看出 本地线程 。那那那它也就不存在去解决并发问题了。

如何使用

我们来看下面的例子。

ThreadLocal可以解决并发问题吗

输出结果:

Thread[Thread-1,5,main]====57

Thread[Thread-0,5,main]====75

创建了两个线程,它们都在 threadlocal 上面都set了一个随机数,我们看最后的输出结果每个都是不同的值,那么我们如果把 threadlocal 替换成一个集合会发生什么,由于两个线程时上个线程生成的随机数57会被第二个线程覆盖掉,而在 Threadlocal 中两个线程都是操作的自己的本地副本,那么两个线程互不影响都无法操控到对方的数据,因此它们存取的都是不同的值。

实现原理

那么 Threadlocal 是如何实现的呢?在研究 Threadlocal 的实现原理我们先看一下 Thread 的内部属性。

ThreadLocal可以解决并发问题吗
  • threadLocals 此线程保存的 Threadlocal 的值

inheritableThreadLocals等到后面再说。

Thread 的内部属性中我们看到了这两个默认为null的属性,threadLocals用来保存 Threadlocal 的本地副本,默认是为null只有调用 Threadlocal 的set时才会创建。也就是说 Threadlocal 就类似一个工具,它的作用就是把value的值通过set存在线程每个线程的threadLocals 中,只要线程一直存在threadLocals 也就一直存在。所以当不需要使用本地变量的时候可以调用 Threadlocal 的remove来清空本地变量。而threadLocals 为什么继承于ThreadLocalMap呢?ThreadLocalMap是一个定制的HashMap,而使用Map的原因就是可以每个线程关联多个 Threadlocal 变量。

set方法

我们来看一下set方法是如何实现的。

ThreadLocal可以解决并发问题吗

可以看出流程非常简单,首先获取当前线程然后在进行下一步操作,我们在看一下getMap做了什么

ThreadLocal可以解决并发问题吗

getMap主要就是返回了当前threadLocals的属性。那如果map为空呢?

ThreadLocal可以解决并发问题吗

如果map为空的话就直接创建一个新的ThreadLocalMap。

我们来看一下流程图。

ThreadLocal可以解决并发问题吗

get方法

看一下Get方法

ThreadLocal可以解决并发问题吗

首先根据当前线程获取实例如果存在就返回,如果不存在就先初始化一个空值,然后判断如果当前threadLoacals不为空就直接set一个空,否则就创建一个变量。

remove方法

ThreadLocal可以解决并发问题吗

remove方法相对来说比较简单。

总结

Threadlocal 的实现原理其实就是通过set把value set到线程的threadlocals属性中, threadlocals 类型是Map其中的Key就是 Threadlocal 的this引用,value就是我们所set的值,如果当前线程不销毁的话 threadlocals 会一直存在。一直存在的话可能会造成内存溢出,所以使用完之后尽量remove一下。不过在这里又有一个问题那就是如果我的线程想要读取主线程的变量要怎么做?我们上面的例子都是设置的新创建的线程,那么现在我在主线程中set一个值,这个时候我在新创建的线程中可以读取到吗?答案是不可以,因为 Threadlocal 不支持继承性。

我们看下面的例子:

ThreadLocal可以解决并发问题吗

输出结果:

Thread[Thread-0,5,main]====null

也就是说 Threadlocal 不支持继承性,主线程设置了值,在子线程中是获取不到的。那我现在想要获取主线程里面的值要怎么做?

Threadlocal 是实现不了的,不过 Threadlocal 有一个子类可以实现。 InheritableThreadLocalInheritableThreadLocalThreadlocal 的实现,我们来看一个简单的例子。

ThreadLocal可以解决并发问题吗

输出结果:

Thread[Thread-0,5,main]====1000

运行结果发现子线程是可以获取到主线程设置的值的,那它是如何实现的?

我们看一下代码实现:

ThreadLocal可以解决并发问题吗

InheritableThreadLocal 是继承 Threadlocal 的,并且把 threadlocals 给替换成 inheritableThreadLocals 了所以上面的 inheritableThreadLocals 我要留在最后说,那么替换成 inheritableThreadLocals 后子线程就可以获取到主线程设置的属性了吗?我们在看一下Thread的实现。

ThreadLocal可以解决并发问题吗

Thread 的初始化方法可以看出,先获取了当前线程(主线程)判断主线程的 inheritableThreadLocals 不为空的话就调用createInheritedMap方法赋值给子线程中的 inheritableThreadLocals 。具体不在这里解释太多。有机会在写一篇文章来解释。

干货分享

最近将个人学习笔记整理成册,使用PDF分享。关注我,回复如下代码,即可获得百度盘地址,无套路领取!

001:《Java并发与高并发解决方案》学习笔记; 002:《深入JVM内核——原理、诊断与优化》学习笔记; 003:《Java面试宝典》 004:《Docker开源书》 005:《Kubernetes开源书》 006:《DDD速成(领域驱动设计速成)》 007: 全部 008: 加技术讨论群

近期热文

想知道更多?长按/扫码关注我吧↓↓↓ ThreadLocal可以解决并发问题吗 >>>技术讨论群<<< 喜欢就点个 "在看" 呗^_^

原文  http://mp.weixin.qq.com/s?__biz=MzI4ODQ3NjE2OA==&mid=2247486710&idx=1&sn=3bf3cb498a64b64f8fb371b8af5b5a58
正文到此结束
Loading...