点击上方 "IT牧场" ,选择 "设为星标"
技术干货每日送达!
到底什么是线程的不安全?为什么会存在线程的不安全?线程的不安全其实就是多个线程并发的去操作同一共享变量没用做同步所产生意料之外的结果。那是如何体现出来的呢?我们看下面的一个非常经典的例子:两个操作员同时操作同一个银行账户,A操作员存钱,100B操作员取钱50。我们看一下流程。
两个操作员同时处理,没用做同步这个时候我们发现银行账户最终余额剩余950元,在我们想的最终结果银行账户应该剩余1000+100-50=1050元,在执行过程中我们没有加锁,最终导致了运行结果偏离预期。那么如何解决的?一般的解决措施就是加锁,加同步锁所以这就需要使用者一定要知道锁是什么。我们来看一下加锁之后的效果是不是我们所预期的。
在添加同步锁后我们可以看到,A操作员和B操作员同时去操作账户,但是A先抢占到资源,所以B就只能等待A操作员释放锁才能去操作银行账户,那么最终结果是我们所预期的吗?答案是的。
同步的话一般都是加锁,如果现在我想创建多个线程每个线程都是访问的自己的变量呢?各个线程之间毫无关联?
答案是有的。
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方法是如何实现的。
可以看出流程非常简单,首先获取当前线程然后在进行下一步操作,我们在看一下getMap做了什么
getMap主要就是返回了当前threadLocals的属性。那如果map为空呢?
如果map为空的话就直接创建一个新的ThreadLocalMap。
我们来看一下流程图。
看一下Get方法
首先根据当前线程获取实例如果存在就返回,如果不存在就先初始化一个空值,然后判断如果当前threadLoacals不为空就直接set一个空,否则就创建一个变量。
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
有一个子类可以实现。 InheritableThreadLocal
, InheritableThreadLocal
是 Threadlocal
的实现,我们来看一个简单的例子。
输出结果:
Thread[Thread-0,5,main]====1000
运行结果发现子线程是可以获取到主线程设置的值的,那它是如何实现的?
我们看一下代码实现:
InheritableThreadLocal
是继承 Threadlocal
的,并且把 threadlocals
给替换成 inheritableThreadLocals
了所以上面的 inheritableThreadLocals
我要留在最后说,那么替换成 inheritableThreadLocals
后子线程就可以获取到主线程设置的属性了吗?我们在看一下Thread的实现。
看 Thread
的初始化方法可以看出,先获取了当前线程(主线程)判断主线程的 inheritableThreadLocals
不为空的话就调用createInheritedMap方法赋值给子线程中的 inheritableThreadLocals
。具体不在这里解释太多。有机会在写一篇文章来解释。
最近将个人学习笔记整理成册,使用PDF分享。关注我,回复如下代码,即可获得百度盘地址,无套路领取!
• 001:《Java并发与高并发解决方案》学习笔记; • 002:《深入JVM内核——原理、诊断与优化》学习笔记; • 003:《Java面试宝典》 • 004:《Docker开源书》 • 005:《Kubernetes开源书》 • 006:《DDD速成(领域驱动设计速成)》 • 007: 全部 • 008: 加技术讨论群
想知道更多?长按/扫码关注我吧↓↓↓ >>>技术讨论群<<< 喜欢就点个 "在看" 呗^_^