ThreadLocal
ThreadLocal
意为线程本地变量,用于解决多线程并发时访问共享变量的问题。
每个线程都会有属于自己的本地内存,在堆(也就是上图的主内存)中的变量在被线程使用的时候会被复制一个副本线程的本地内存中,当线程修改了共享变量之后就会通过JMM
管理控制写会到主内存中。
ThreadLoal 变量,线程局部变量,同一个 ThreadLocal
所包含的对象,在不同的 Thread
中有不同的副本。这里有几点需要注意:
- 因为每个
Thread
内有自己的实例副本,且该副本只能由当前Thread
使用。这是也是ThreadLocal
命名的由来。 - 既然每个
Thread
有自己的实例副本,且其它Thread
不可访问,那就不存在多线程间共享的问题。
很明显,在多线程的场景下,当有多个线程对共享变量进行修改的时候,就会出现线程安全问题,即数据不一致问题。常用的解决方法是对访问共享变量的代码加锁(synchronized
或者Lock
)。但是这种方式对性能的耗费比较大。在JDK1.2
中引入了ThreadLocal
类,来修饰共享变量,使每个线程都单独拥有一份共享变量,这样就可以做到线程之间对于共享变量的隔离问题。
当然锁和ThreadLocal
使用场景还是有区别的,具体区别如下:
synchronized(锁) | ThreadLocal | |
---|---|---|
原理 | 同步机制采用了时间换空间的方式,只提供一份变量,让不同线程排队访问(临界区排队) | 采用空间换时间的方式,为每一个线程都提供一份变量的副本,从而实现同时访问而互不相干扰 |
侧重点 | 多个线程之间访问资源的同步 | 多线程中让每个线程之间的数据相互隔离 |
二、原理
每个Thread
对象都有一个ThreadLocalMap
,当创建一个ThreadLocal
的时候,就会将该ThreadLocal
对象添加到该Map
中,其中键就是ThreadLocal
,值可以是任意类型。
前面我们的理解是所有的常量值或者是引用类型的引用都是保存在ThreadLocal
实例中的,但实际上不是的,这种说法只是让我们更好的理解ThreadLocal
变量这个概念。
向ThreadLocal
存入一个值,实际上是向当前线程对象中的ThreadLocalMap
存入值,ThreadLocalMap
我们可以简单的理解成一个Map
,而向这个Map
存值的key
就是ThreadLocal
实例本身。
Key的弱引用问题
为什么要用弱引用,官方是这样回答的
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了处理非常大和生命周期非常长的线程,哈希表使用弱引用作为key。
生命周期长的线程可以理解为:线程池的核心线程
ThreadLocal
在没有外部对象强引用时如Thread
,发生GC
时弱引用Key
会被回收,而Value
是强引用不会回收,如果创建ThreadLocal
的线程一直持续运行如线程池中的线程,那么这个Entry
对象中的value
就有可能一直得不到回收,发生内存泄露。
key
使用强引用: 引用的ThreadLocal
的对象被回收了,但是ThreadLocalMap
还持有ThreadLocal
的强引用,如果没有手动删除,ThreadLocal
不会被回收,导致Entry
内存泄漏。key
使用弱引用: 引用的ThreadLocal
的对象被回收了,由于ThreadLocalMap
持有ThreadLocal
的弱引用,即使没有手动删除,ThreadLocal
也会被回收。value
在下一次ThreadLocalMap
调用set
,get
,remove
的时候会被清除。
Java8
中已经做了一些优化如,在ThreadLocal
的get()
、set()
、remove()
方法调用的时候会清除掉线程ThreadLocalMap
中所有Entry
中Key
为null
的Value
,并将整个Entry
设置为null
,利于下次内存回收。