ThreadLocal原理与内存泄漏问题

ThreadLocal原理


当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。

存储结构

public class Thread implements Runnable {
......
    ThreadLocal.ThreadLocalMap threadLocals = null;//一个线程对应一个ThreadLocalMap
......
}
public class ThreadLocal<T> {
......
    static class ThreadLocalMap {//静态内部类
        static class Entry extends WeakReference<ThreadLocal> {//键值对
            //Entry是ThreadLocal对象的弱引用,this作为键(key)
            /** The value associated with this ThreadLocal. */
            Object value;//ThreadLocal关联的对象,作为值(value),也就是所谓的线程本地变量
 
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        ......
        private Entry[] table;//用数组保存所有Entry,采用线性探测避免冲突
    }
......
}

ThreadLocal主要方法

public T get() { }  
public void set(T value) { }  
public void remove() { }  
protected T initialValue() { }  

 

内存泄漏问题

对于ThreadLocalMap 的键值对Entry,key为ThreadLocal实例,value为线程本地变量。而Entry继承自WeakReference<ThreadLocal>。WeakReference为弱引用,也就是说Key是一个弱引用(引用ThreadLocal实例)。

为什么使用弱引用

To help deal with very large and long-lived usages, the hash table entries use  WeakReferences for keys.

下面看一段示例:

public class Test {
    public static class MyThreadLocal extends ThreadLocal {
        private byte[] a = new byte[1024*1024*1];
        
        @Override
        public void finalize() {
            System.out.println("My threadlocal 1 MB finalized.");
        }
    }
    
    public static class My50MB {//占用内存的大对象
        private byte[] a = new byte[1024*1024*50];
        
        @Override
        public void finalize() {
            System.out.println("My 50 MB finalized.");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                ThreadLocal tl = new MyThreadLocal();
                tl.set(new My50MB());
                
                tl=null;//断开ThreadLocal的强引用
                System.out.println("Full GC 1");
                System.gc();
                
            }
            
        }).start();
        System.out.println("Full GC 2");
        System.gc();
        Thread.sleep(1000);
        System.out.println("Full GC 3");
        System.gc();
        Thread.sleep(1000);
        System.out.println("Full GC 4");
        System.gc();
        Thread.sleep(1000);
 
    }
}

输出:

Full GC 2
Full GC 1
My threadlocal 1 MB finalized.
Full GC 3
My 50 MB finalized.
Full GC 4

可以看出,当key置为null时,threadLocal的强引用断开,key的内存就可以在下一次GC之后释放。而value的值要等到线程销毁时才释放。

当遇到线程不会被回收的情况时,比如使用线程池,就可能会发生内存泄漏。

解决方法

每次执行完毕后,要使用remove()方法来清空对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值