ThreadLocal可以解决并发问题吗

36次阅读

共计 2855 个字符,预计需要花费 8 分钟才能阅读完成。

前言

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

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

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

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

答案是有的。

ThreadLocal 问题

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

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

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

自己的想法

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

如何使用

我们来看下面的例子。

输出结果:

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

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

实现原理

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

  • 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 方法是如何实现的。

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

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

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

我们来看一下流程图。

get 方法

看一下 Get 方法

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

remove 方法

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

总结

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

我们看下面的例子:

输出结果:

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

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

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

输出结果:

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

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

我们看一下代码实现:

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

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

本文完

正文完
 0