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使用场景)

浙公网安备 33010602011771号