多线程,为每个线程维护变量副本的ThreadLocal,ThreadLocal会造成内存泄漏吗?

1.简单介绍

在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

下面是示例:

public class ThreadLocalTest {
	
	 /*1通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值  */
    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {  
        public Integer initialValue() {  
            return 0;  
        }  
    };  
  
    /*2获取下一个序列值  */
    public int getNextNum() {  
        seqNum.set(seqNum.get() + 1);  
        return seqNum.get();  
    }  
	public static void main(String args[]){
		System.out.println("ThreadLocalTest start");
		ThreadLocalTest sn = new ThreadLocalTest();  
        /*3个线程共享sn,各自产生序列号  */
        TestClient t1 = new TestClient(sn);  
        TestClient t2 = new TestClient(sn);  
        TestClient t3 = new TestClient(sn);  
        t1.start();  
        t2.start();  
        t3.start(); 
	}
	private static class TestClient extends Thread {  
        private ThreadLocalTest sn;  
  
        public TestClient(ThreadLocalTest sn) {  
            this.sn = sn;  
        }  
  
        public void run() {  
            for (int i = 0; i < 3; i++) {  
                /*4每个线程打出3个序列值  */
                System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn["  
                         + sn.getNextNum() + "]");  
            }  
        }  
    }  
}

使用例子:

	/**sdf有全局变量线程不安全,用ThreadLocal提供线程安全的sdf*/
	public static final ThreadLocal<DateFormat> SDF = new ThreadLocal<DateFormat>() {
		@Override
		protected DateFormat initialValue() {
			return new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
		}
	};

2.比较优秀的使用例子2(重要):

public class Port implements IThreadCase{

	//当前线程的Port实例
	private final static ThreadLocal<Port> portCurrent = new ThreadLocal<>();
	//线程管理类
	private final ThreadHandler thread;
	public Port(){
		this.thread = new ThreadHandler(this);
	}
	/** 开始 */
	public Port startup() {
		// 启动独立线程
		this.thread.setName(toString());
		this.thread.startup();
		return this;
	}
	/** 结束 */
	public void stop() {
		if (thread == null)
			return;
		// 停止独立线程
		this.thread.cleanup();
	}
	
	/** 获取当前线程的Port实例  */
	@SuppressWarnings("unchecked")
	public static <T extends Port> T getCurrent() {
		return (T) portCurrent.get();
	}
	
	@Override
	public void caseStart() {
		portCurrent.set(this);
	}
	@Override
	public void caseStop() {
		portCurrent.set(null);
	}
}


3. ThreadLocal会造成内存泄漏吗?

不同线程反复对ThreadLocal设置自己的值,线程会自己结束,但是没有对ThreadLocal存储本线程的内容做移除,会造成内存泄漏吗。

答案是不会的。

示例代码:

public class ThreadLocalTest {

    public static final int _10MB = 1024  * 1024 * 10;

    public static ThreadLocal<Object> threadLocal = new ThreadLocal(){
        @Override
        public Object initialValue(){
            return null;
        }
    };

    public static void main(String args[]){System.out.println();
        for(;;){
            new Thread(){
                @Override
                public void run() {
                    byte[] data = new byte[_10MB];
                    threadLocal.set(data);
                }
            }.start();
            ThreadTool.sleep(1000);
            System.out.println(Sys.getJVMStatus());
        }
    }
}
运行结果:

maxM=3641 MB, totalM=245.5 MB, freeM=231.66 MB, usedM=13.84 MB
maxM=3641 MB, totalM=245.5 MB, freeM=220.38 MB, usedM=25.12 MB
maxM=3641 MB, totalM=245.5 MB, freeM=209.1 MB, usedM=36.4 MB
maxM=3641 MB, totalM=245.5 MB, freeM=197.82 MB, usedM=47.68 MB
maxM=3641 MB, totalM=245.5 MB, freeM=186.54 MB, usedM=58.96 MB
maxM=3641 MB, totalM=245.5 MB, freeM=233.51 MB, usedM=11.99 MB
maxM=3641 MB, totalM=245.5 MB, freeM=222.22 MB, usedM=23.28 MB
maxM=3641 MB, totalM=245.5 MB, freeM=211.58 MB, usedM=33.92 MB
maxM=3641 MB, totalM=245.5 MB, freeM=200.94 MB, usedM=44.56 MB
maxM=3641 MB, totalM=245.5 MB, freeM=190.3 MB, usedM=55.2 MB
maxM=3641 MB, totalM=245.5 MB, freeM=232.87 MB, usedM=12.63 MB

maxM=3641 MB, totalM=245.5 MB, freeM=221.16 MB, usedM=24.34 MB
maxM=3641 MB, totalM=245.5 MB, freeM=209.88 MB, usedM=35.62 MB
maxM=3641 MB, totalM=245.5 MB, freeM=198.6 MB, usedM=46.9 MB
maxM=3641 MB, totalM=245.5 MB, freeM=187.32 MB, usedM=58.18 MB
maxM=3641 MB, totalM=245.5 MB, freeM=233.56 MB, usedM=11.94 MB
maxM=3641 MB, totalM=245.5 MB, freeM=222.65 MB, usedM=22.85 MB
maxM=3641 MB, totalM=245.5 MB, freeM=212.01 MB, usedM=33.49 MB
maxM=3641 MB, totalM=245.5 MB, freeM=201.37 MB, usedM=44.13 MB
maxM=3641 MB, totalM=245.5 MB, freeM=190.73 MB, usedM=54.77 MB
maxM=3641 MB, totalM=245.5 MB, freeM=234.2 MB, usedM=11.3 MB
maxM=3641 MB, totalM=245.5 MB, freeM=222.74 MB, usedM=22.76 MB
maxM=3641 MB, totalM=245.5 MB, freeM=211.46 MB, usedM=34.04 MB
maxM=3641 MB, totalM=245.5 MB, freeM=200.18 MB, usedM=45.32 MB
maxM=3641 MB, totalM=245.5 MB, freeM=188.9 MB, usedM=56.6 MB
maxM=3641 MB, totalM=234.5 MB, freeM=222.59 MB, usedM=11.91 MB

maxM=3641 MB, totalM=234.5 MB, freeM=211.23 MB, usedM=23.27 MB

原因分析:

我们看下get和Set

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

 
ThreadLocal.ThreadLocalMap.Entry是啥?

  /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

因为继承了WeakRefrence<?>所以是弱引用,每次GC的时候“总会”被回收。

但是如果不remove会不会有问题呢?

会的。比如在“线程池”的场景下,如果每次不提前set值而只是覆盖了initValue(),如果不remove会造成“意外”的结果。



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值