并发编程-同步机制(三)

三、ThreadLocal 线程本地变量

ThreadLocal 与Synchonized 都是用于解决多线程并发安全问题的,只是synchronized利用的锁的机制,使变量或代码块在某一时刻只能由一个线程访问。

ThreadLocal 为每个线程提供了变量的副本,副本只有所属线程才可以操作。这样就隔离了多个线程对数据的数据共享。

spring 的事务就是借助ThreadLocal类,后续spring 源码篇会详细解释。

ThreadLocal 使用起来很简单,常用只有4个方法:

public T get():获取当前线程所对应的线程局部变量。

public void set(T value):设置当前线程的线程局部变量的。

public void remove():删除当前线程局部变量,@since 1.5 新加方法。我们知道ThreadLocal 是线程局部变量,所以说线程销毁后GC 会回收ThreadLocal,所以这个方法非必须调用,使用它只是为了加快内存回收的速度。

protected T initialValue():返回当前线程的“初始值”,该方法是一个protected,显然是为了让子类重新的而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() 或 set(Object)时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一 个 null

ThreadLocal 解析

    public T get() {
        Thread t = Thread.currentThread(); //获取当前线程
        ThreadLocalMap map = getMap(t); //获取线程中的ThreadLocalMap 
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

上面线程先是获取当前线程,然后调用了getMap(t) 获取了线程中的ThreadLocalMap(面试重灾区)。

ThreadLocalMap 是ThreadLocal 的静态内部类,然而ThreadLocalMap 并不属于ThreadLocal 而是需要传入Thread 

 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

  public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

我们打开Thread 类

 /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap  是Thread 的成员变量。

那么ThreadLocalMap 的内部实现又是什么呢?


    static class ThreadLocalMap {
        //WeakReference 弱引用。
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            //类似map 的key,value 结构,其中的key 是ThreadLocal
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

内部维护了一个Entry的内部类。Entry 类似map 的K,V 结构,其中的key 是ThreadLocal,value就是需要保存的变量。

       /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

使用数组保存了Entry,初始的容量为16,由于可能保存的变量为多个。

 private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

getEntry 方法 则是获取某个 ThreadLocal 对应的值,set 方法就是更新或赋值相应的 ThreadLocal 对应的值。

    public T get() {
        Thread t = Thread.currentThread(); //获取当前线程
        ThreadLocalMap map = getMap(t); //获取线程中的ThreadLocalMap 
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue(); //如果没有拿到map 那么进行初始化
    }

 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
 private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

回到我们get 方法,其实就是拿到每个线程的ThreadLocalMap,然后通过ThreadLocal为key获取到对应的value。如果没有获取到map 的话那么进行初始化等一些工作。

由于ThreadLocal 使用比较简单就不做演示了,基本就是定义全局变量就可以了

 private static ThreadLocal<T> value = new ThreadLocal<T>() {
    };

内存泄漏分析

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

我们可以看到ThreadLocalMap 的Entry 继承了一个WeakReference 弱引用,所以就为了ThreadLocal 使用不当引起的内存泄漏埋下伏笔了。java中的引用类型分为

强引用:就是指在程序代码之中普遍存在的,类似“Object obj=new Object()” 这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象实例。

软引用:是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象, 在系统将要发生内存溢出异常之前,将会把这些对象实例列进回收范围之中进行 第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在 JDK 1.2 之后,提供了 SoftReference 类来实现软引用。

弱引用:也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱 引用关联的对象实例只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时, 无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。在 JDK 1.2 之 后,提供了 WeakReference 类来实现弱引用。

虚引用:也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象 实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用 来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象 实例被收集器回收时收到一个系统通知。在 JDK 1.2 之后,提供了 PhantomReference 类来实现虚引用。

场景一 

使用线程池投入无业务线程 设置最大堆大小为 -Xmx256m


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalTest {

	private static ThreadLocal<LocalVariable> local;

	private static ExecutorService pool = Executors.newFixedThreadPool(5);

	//本地变量填充
	static class LocalVariable {
		private byte[] a = new byte[1024 * 1024  * 5];/* 5M大小的数组 */
	}
	
	static class MyThread implements Runnable {

		public void run() {
			// TODO Auto-generated method stub
//			local = new ThreadLocal<ThreadLocalTest.LocalVariable>();
//			local.set(new LocalVariable());
//			new LocalVariable();
//			local.remove();
			System.out.println("MyThread start");
		}
	}

	public static void main(String[] args) throws Exception {
		Thread.sleep(2000);
		
		for(int i=0;i<100;i++) {
			Thread.sleep(200);
			pool.execute(new MyThread());
		}
		Thread.sleep(2000);
		System.out.println("执行结束了");

	}

}

 堆使用一直在25m 左右。

场景二

在线程中投入填充5m 大小byte 


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalTest {

	private static ThreadLocal<LocalVariable> local;

	private static ExecutorService pool = Executors.newFixedThreadPool(5);

	//本地变量填充
	static class LocalVariable {
		private byte[] a = new byte[1024 * 1024  * 5];/* 5M大小的数组 */
	}
	
	static class MyThread implements Runnable {

		public void run() {
			// TODO Auto-generated method stub
//			local = new ThreadLocal<ThreadLocalTest.LocalVariable>();
//			local.set(new LocalVariable());
			new LocalVariable();
//			local.remove();
			System.out.println("MyThread start");
		}
	}

	public static void main(String[] args) throws Exception {
		Thread.sleep(2000);
		
		for(int i=0;i<100;i++) {
			Thread.sleep(200);
			pool.execute(new MyThread());
		}
		Thread.sleep(2000);
		System.out.println("执行结束了");

	}

}

 

 

 内存使用跟一类似。

场景三

使用ThreadLocal 保存数据


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalTest {

	private static ThreadLocal<LocalVariable> local;

	private static ExecutorService pool = Executors.newFixedThreadPool(5);

	//本地变量填充
	static class LocalVariable {
		private byte[] a = new byte[1024 * 1024  * 5];/* 5M大小的数组 */
	}
	
	static class MyThread implements Runnable {

		public void run() {
			// TODO Auto-generated method stub
			local = new ThreadLocal<ThreadLocalTest.LocalVariable>();
			local.set(new LocalVariable());
		//	new LocalVariable();
//			local.remove();
			System.out.println("MyThread start");
		}
	}

	public static void main(String[] args) throws Exception {
		Thread.sleep(2000);
		
		for(int i=0;i<100;i++) {
			Thread.sleep(200);
			pool.execute(new MyThread());
		}
		Thread.sleep(2000);
		System.out.println("执行结束了");

	}

}

内存变为了100M 左右

 

场景四

使用ThreadLocal 保存数据,调用remove 方法


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalTest {

	private static ThreadLocal<LocalVariable> local;

	private static ExecutorService pool = Executors.newFixedThreadPool(5);

	//本地变量填充
	static class LocalVariable {
		private byte[] a = new byte[1024 * 1024  * 5];/* 5M大小的数组 */
	}
	
	static class MyThread implements Runnable {

		public void run() {
			// TODO Auto-generated method stub
			local = new ThreadLocal<ThreadLocalTest.LocalVariable>();
			local.set(new LocalVariable());
			//	new LocalVariable();
			local.remove();
			System.out.println("MyThread start");
		}
	}

	public static void main(String[] args) throws Exception {
		Thread.sleep(2000);
		
		for(int i=0;i<100;i++) {
			Thread.sleep(200);
			pool.execute(new MyThread());
		}
		Thread.sleep(2000);
		System.out.println("执行结束了");

	}

}

 内存基本跟场景一二保持一致

由此可以看到场景三使用ThreadLocal 发生的内存泄漏。由于ThreadLocal 内存泄漏原因分析网上一堆,这边就不分析了可以参考 ThreadLocal为什么会导致内存泄漏? - Chen洋 - 博客园

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值