ThreadLocal

ThreadLocal


该类提供了线程局部(thread-local)变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其getset方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。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中就会出现keynullEntry,就没有办法访问这些keynullEntryvalue,如果当前线程再迟迟不结束的话,这些keynullEntryvalue就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。

其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocalget(),set(),remove()的时候都会清除线程ThreadLocalMap里所有keynullvalue

注意

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://cmsblogs.com/?p=2442

http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/(内存泄漏问题)

https://blog.csdn.net/sonny543/article/details/51336457(ThreadLocal使用场景)

posted @ 2018-09-25 20:11  一把水果刀  阅读(198)  评论(0编辑  收藏  举报