你应该搞懂的ThreadLocal解析

JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal并不是一个Thread,而是Thread的局部变量

ThreadLocal可以解决

  • 多线程并发问题
  • 数据传递
  • 线程隔离

ThreadLocal与Synchronized关键字的区别

  1. ThreadLocal和Synchronized都可以解决多线程并发问题。
  2. ThreadLocal采用的是以空间换时间,每个线程都提供一个变量副本,从而实现访问互不干扰。
  3. Synchronized采用的是以时间换空间的方式,只提供一个变量让线程进行排队访问。
  4. ThreadLocal可以提供更好的并发性

ThreadLocal内部结构
早先jdk设计ThreadLocal是,每一个ThreadLocal都会创建一个Map,当前线程(Thread)作为Map的key,要存储的局部变量作为map的value,这样就能达到各个线程局部变量隔离。
jdk8 ThreadLocal是,每一个Thread维护一个ThreadLocalMap,Map的key就是ThreadLocal,要存储的局部变量作为map的value,来能达到各个线程局部变量隔离。
ThreadLoacl jdk8 ThreadLocal优化的好处:

  • 每个Map存储的Entry数量变少,Hash碰撞随之减少。
  • Thread销毁之后,ThreadLocalMap也会随之销毁,减少内存开销。

ThreadLocal不是封装在对象内,而是放在线程中

public T get() {
   Thread t = Thread.currentThread();
   // 从线程Thread中获得ThreadLocal,再ThreadLocal中获得ThreadLocalMap 
   ThreadLocalMap map = getMap(t);
   if (map != null) {
      // key:ThreadLocal,获取vlaue
      ThreadLocalMap.Entry e = map.getEntry(this);
      if (e != null) {
         @SuppressWarnings("unchecked")
         T result = (T)e.value;
            return result;
       }
    }
    /** 
     * 初始化
     * 1.map不存在,当前线程没有关联ThreadLocalMap 对象
     * 2.map存在,但是没有与当前ThreadLocal关联的entry
     * */
     return setInitialValue();
}

public void set(T value) {
  Thread t = Thread.currentThread();
   // 从线程Thread中获得ThreadLocal,再ThreadLocal中获得ThreadLocalMap 
  ThreadLocalMap map = getMap(t);
  if (map != null)
     map.set(this, value);
   else
     // 创建一个Map,并设置初始值
     createMap(t, value);
}

threadLocals是一个ThreadLocal.ThreadLocalMap对象,具有很多Map的特性

// 获取当前线程Thread对应维护的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}
/**
 * 创建当前线程Thread对应维护的ThreadLocalMap
 * @param t 当前线程
 * @param firstValue 存在在map中的第一个entry值
 * */
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
  • ThreadLocalMap是ThreadLocal的一个静态内部类,ThreadLocalMap内部持有一个Entry数组默认大小16,threadLocal本质上实现了一个简易的map
    // Entry初始化容量--必须是2的整数幂,有利于散列分布,减少hash碰撞
    private static final int INITIAL_CAPACITY = 16;
    // 存放数据的entry
    private Entry[] table;
    // 进行扩容的阀值,默认 len * 2 / 3 = 10
    private int threshold;
    
  • Entry是一个继承了WeakReference的键值对,key是ThreadLocal变量的一个引用,value是保存的值
     static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
           super(k);
           value = v;
           }
    }
    

弱引用与内存泄漏

  • 内存泄漏
  1. Memory overflow:内存溢出,没有足够的内存空间提供申请者使用。
  2. Memory leak:内存泄漏指程序中以动态分配的堆内存由于某种原因程序未释放,造成系统内存的浪费,导致程序运行速度慢,甚至系统崩溃等,内存泄漏的堆积将导致内存溢出。
  • 弱引用
  1. java中的引用类型有4种:强、软、弱、虚,当前主要涉及强引用和弱引用。
  2. 强引用("Strong"Reference),就是我们最常见的普通对象应用,只要有强引用指向一个对象,就能表明对象还"活着",垃圾回收器就不会回收这种对象。
  3. 弱引用(WeakReference),垃圾回收器一旦发现,只有一个弱引用的对象,不管当前内存空间是否足够,都会回收它。
  • ThreadLocalMap key使用弱引用,如何减少内存泄漏?哪里内存泄漏?
    ThreadLocal弱引用
  1. key使用强引用:引用的ThreadLocal被回收了,但是ThreadLocalMap的ThreadLocal还持有强引用,如果没有手动回收就会存在内存泄漏。
  2. key使用弱引用:引用的ThreadLocal被回收了,由于ThreadLocalMap只是持有ThreadLocal的弱引用,如果没有手动删除,在下次回收时会被销毁(key),避免了内存泄漏。
  3. 使用弱引用是不是就没有内存泄漏?当然不是,前面只是把key回收了,整个Entry还没有被回收。
  4. 使用弱引用ThreadLocal内存泄漏的根本原因是:ThreadLocalMap的生命周期和Thread一样长,如果没有手动删除对应key就会导致内存泄漏,但是弱引用比强引用多一层保障,在下次调用ThreadLocalMap的get、set、remove、rehash等方法都会主动清理key为null的Entry。

ThreadLocalMap hash冲突
ThreadLocalMap 中的set方法

private void set(ThreadLocal<?> key, Object value) {
	    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();
	        // 如果key存在,直接覆盖之前的值
	        if (k == key) {
	            e.value = value;
	            return;
	        }
	        // key是空,但是值不为空,说明ThreadLocal已经被回收,当前value需要清理
	        // 当前数组中的Entry是一个旧的(stale)元素
	        if (k == null) {
	            // 用新元素替换旧的,并且进行一些垃圾清理工作,防止内存泄漏
	            replaceStaleEntry(key, value, i);
	            return;
	           }
	    }
	    // 对应的key不存在,并且没有陈旧的元素,则创建一个新的Entry
	    tab[i] = new Entry(key, value);
	    int sz = ++size;
	    /** 
	     * cleanSomeSlots用于清理e.get()==null的元素,这种key关联的对象已经被回收,
	     * 所以Entry(table[index])可以置为空,如果没有清理任何entry,并且当前使用负载因子
	     * 达到长度2/3,进行rehash(执行全表扫描清理工作)
	     * entry size个数达到(size >= threshold - threshold / 4)进行扩容,Entry扩容为原先的2倍
	    **/
	    if (!cleanSomeSlots(i, sz) && sz >= threshold)
	        rehash();
	}
	// 获取环形数组的下一个索引
	private static int nextIndex(int i, int len) {
	            return ((i + 1 < len) ? i + 1 : 0);
	}
	private void rehash() {
       xpungeStaleEntries();

       // Use lower threshold for doubling to avoid hysteresis
       if (size >= threshold - threshold / 4)
           resize();
   }

ThreadLoalMap使用线性探测法来解决哈希冲突,该方法一次探测下一个地址,直到有空的地址插入,如果整个空间都找不到空余的地址,则产生溢出。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值