码迷,mamicode.com
首页 > 编程语言 > 详细

Java学习2 (ThreadLocal)

时间:2020-06-12 20:12:36      阅读:68      评论:0      收藏:0      [点我收藏+]

标签:而不是   辅助   shm   存在   remove   需要   aci   object   为什么   

ThreadLocal

每个线程的变量副本是存储在哪里的

  • ThreadLocal(线程局部变量)
  • 在线程之间共享变量是存在风险的,有时可能要避免共享变量,使用ThreadLocal辅助类为各个线程提供各自的实例。
  • 如果加锁,那么会开销很大

实现原理:

  • ThreadLocal的get方法就是从当前线程的ThreadLocalMap中取出当前线程对应的变量的副本。该Map的key是ThreadLocal对象,value是当前线程对应的变量。
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();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
  • 变量是保存在线程中的,而不是保存在ThreadLocal变量中
  • 每个线程都有一个这样的名为threadLocals 的ThreadLocalMap,以ThreadLocal和ThreadLocal对象声明的变量类型作为key和value。
  • 这样,我们所使用的ThreadLocal变量的实际数据,通过get方法取值的时候,就是通过取出Thread中threadLocals引用的map,然后从这个map中根据当前threadLocal作为参数,取出数据。现在,变量的副本从哪里取出来的(本文章提出的第一个问题)已经确认解决了。
  • 每个线程内部都会维护一个类似 HashMap 的对象,称为 ThreadLocalMap,里边会包含若干了 Entry(K-V 键值对),相应的线程被称为这些 Entry 的属主线程;
  • Entry 的 Key 是一个 ThreadLocal 实例,Value 是一个线程特有对象。Entry 的作用即是:为其属主线程建立起一个 ThreadLocal 实例与一个线程特有对象之间的对应关系;
  • Entry 对 Key 的引用是弱引用;Entry 对 Value 的引用是强引用。

为什么ThreadLocalMap的Key是弱引用

  • 如果是强引用,ThreadLocal将无法被释放内存。
  • 因为如果这里使用普通的key-value形式来定义存储结构,实质上就会造成节点的生命周期与线程强绑定,只要线程没有销毁,那么节点在GC分析中一直处于可达状态,没办法被回收,而程序本身也无法判断是否可以清理节点。弱引用是Java中四档引用的第三档,比软引用更加弱一些,如果一个对象没有强引用链可达,那么一般活不过下一次GC。当某个ThreadLocal已经没有强引用可达,则随着它被垃圾回收,在ThreadLocalMap里对应的Entry的键值会失效,这为ThreadLocalMap本身的垃圾清理提供了便利。

ThreadLocalMap是何时初始化的(setInitialValue)

在get时最后一行调用了setInitialValue,它又调用了我们自己重写的initialValue方法获得要线程局部变量对象。ThreadLocalMap没有被初始化的话,便初始化,并设置firstKey和firstValue;如果已经被初始化,那么将key和value放入map。

ThreadLocalMap 原理

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

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
  • 它也是一个类似HashMap的数据结构,但是并没实现Map接口
  • 也是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象,通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中。
  • ThreadLoalMap的Entry是继承WeakReference,和HashMap很大的区别是,Entry中没有next字段,所以就不存在链表的情况了。所以如果存在hash冲突则使用的是开放寻址法。
  • 在ThreadLocalMap中,形如key.threadLocalHashCode & (table.length - 1)(其中key为一个ThreadLocal实例)这样的代码片段实质上就是在求一个ThreadLocal实例的哈希值,只是在源码实现中没有将其抽为一个公用函数。
    对于& (INITIAL_CAPACITY - 1),相对于2的幂作为模数取模,可以用&(2n-1)来替代%2n,位运算比取模效率高很多。至于为什么,因为对2^n取模,只要不是低n位对结果的贡献显然都是0,会影响结果的只能是低n位。
private void setThreshold(int len) {
    threshold = len * 2 / 3;
}

内存泄露

  • 只有调用TheadLocal的remove或者get、set时才会采取措施去清理被回收的ThreadLocal对应的value(但也未必会清理所有的需要被回收的value)。假如一个局部的ThreadLocal不再需要,如果没有去调用remove方法清除,那么有可能会发生内存泄露。
  • 既然已经发现有内存泄露的隐患,自然有应对的策略,在调用ThreadLocal的get()、set()可能会清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有GC Roots可达了,下次GC的时候就可以被回收,当然如果调用remove方法,肯定会删除对应的Entry对象。
  • 如果使用ThreadLocal的set方法之后,没有显式的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法
  • JDK建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。

Java学习2 (ThreadLocal)

标签:而不是   辅助   shm   存在   remove   需要   aci   object   为什么   

原文地址:https://www.cnblogs.com/yankang/p/13109803.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!