ThreadLocal
ThreadLocal
该类提供了线程局部(thread-local)变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其
get
或set
方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal
实例通常是类中的私人静字段,它们希望将状态与某一个线程(例如,用户ID或事务ID)相关联。
其公共方法只有三个:
- public T get() { } 返回此线程局部变量的当前线程副本中的值。
- public void set(T value) { } 将此线程局部变量的当前线程副本中的值设置为指定值。
- public void remove() { } 移除此线程局部变量当前线程的值。
- protected T initialValue() { } initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法
ThreadLocal内部还有一个静态内部类ThreadLocalMap,该内部类才是实现线程隔离机制的关键,get()、set()、remove()都是基于该内部类操作。ThreadLocalMap提供了一种用键值对方式存储每一个线程的变量副本的方法,key为当前ThreadLocal对象,value则是对应线程的变量副本。
ThreadLocal
本身并不存储值,它只是作为一个 key
来让线程从 ThreadLocalMap
获取 value
。值得注意的是图中的虚线,表示 ThreadLocalMap
是使用 ThreadLocal
的弱引用作为 Key
的,弱引用的对象在 GC 时会被回收。
ThreadLocal内存泄漏
ThreadLocalMap
使用ThreadLocal
的弱引用作为key
,如果一个ThreadLocal
没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal
势必会被回收,这样一来,ThreadLocalMap
中就会出现key
为null
的Entry
,就没有办法访问这些key
为null
的Entry
的value
,如果当前线程再迟迟不结束的话,这些key
为null
的Entry
的value
就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄漏。
其实,ThreadLocalMap
的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal
的get()
,set()
,remove()
的时候都会清除线程ThreadLocalMap
里所有key
为null
的value
。
注意
ThreadLocal
内存泄漏的根源是:由于ThreadLocalMap
的生命周期跟Thread
一样长,如果没有手动删除对应key
就会导致内存泄漏,而不是因为弱引用。
ThreadLocal 最佳实践
综合上面的分析,我们可以理解ThreadLocal
内存泄漏的前因后果,那么怎么避免内存泄漏呢?
- 每次使用完
ThreadLocal
,都调用它的remove()
方法,清除数据。
在使用线程池的情况下,没有及时清理ThreadLocal
,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal
就跟加锁完要解锁一样,用完就清理。
使用案例
package my.syn;
import java.util.concurrent.CountDownLatch;
import static my.syn.MyGame.countDownLatch;
import static my.syn.MyGame.threadLocal;
/**
* @ClassName: MyGame
* @author: Yang.X.P
* @date: 2018-09-17 16:24
**/
public class MyGame implements Runnable{
static CountDownLatch countDownLatch = new CountDownLatch(4);
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
@Override
public void run() {
System.out.println("游戏大厅已加载好,等待所有玩家准备好,即可进入");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有人到准备好了,游戏开始...");
}
}
class Aircraft implements Runnable {
@Override
public void run() {
System.out.println("飞机准备好了,等待起飞");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("玩家到齐,飞机起飞!");
}
}
class Player implements Runnable {
@Override
public void run() {
threadLocal.set(Thread.currentThread().getName() + "的threadLocal");
System.out.println(threadLocal.get());
threadLocal.remove();
System.out.println(Thread.currentThread().getName() + "准备好了");
countDownLatch.countDown();
}
public static void main(String[] args){
Player player = new Player();
MyGame myGame = new MyGame();
Aircraft aircraft = new Aircraft();
Thread airThread = new Thread(aircraft);
Thread gameThread = new Thread(myGame);
gameThread.start();
airThread.start();
for (int i = 0; i < 4; i++) {
Thread playerThread = new Thread(player);
playerThread.start();
}
}
}
参考资料:
http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/(内存泄漏问题)
https://blog.csdn.net/sonny543/article/details/51336457(ThreadLocal使用场景)