Java并发编程之ThreadLocal详解

来源:互联网 发布:三大龙脉 知乎 编辑:程序博客网 时间:2024/05/16 06:45

ThreadLocal是什么?


  ThreadLocal是一个关于创建线程局部变量的类。

  通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。

ThreadLocal使用示例


示例1:ThreadLocal声明基本类型变量

示例1 代码段(1)

示例1 代码段(2)

  执行程序,可以得到:

示例1 执行结果

  从运行结果可以看出,对于基本类型变量,ThreadLocal确实是可以达到线程隔离作用的。

示例2:ThreadLocal声明自定义类型的对象

示例2 自定义类型

示例2 代码段(1)

示例2 代码段(2)

  执行程序,可以得到:

示例2 运行结果

  从运行结果可以看出,对于自定义类型的对象,ThreadLocal也是可以达到线程隔离作用的。

示例3:ThreadLocal声明的变量都指向同一个对象

示例3 对程序代码稍作修改

  对示例2的代码稍作修改,使得ThreadLocal声明的变量初始化时不再实例化一个新的对象,而是让它指向同一个对象,运行查看结果:

示例3 运行结果

  很显然,在这里,并没有通过ThreadLocal达到线程隔离的机制,可是ThreadLocal不是保证线程安全的么?这是什么鬼? 显然,虽说ThreadLocal让访问某个变量的线程都拥有自己的局部变量,但是如果这个局部变量都指向同一个对象的话,这个时候,ThreadLocal就失效了。

ThreadLocal源码剖析


  ThreadLocal类的源码在java.lang包中。其中主要有四个方法:

1. get()

// 返回当前线程所对应的线程变量public T get() {    // 获取当前线程    Thread t = Thread.currentThread();    // 获取当前线程的成员变量 threadLocal    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();}

  从源码中可以看到,get()方法首先通过当前线程获取所对应的成员变量ThreadLocalMap,然后通过ThreadLocalMap获取当前ThreadLocal的键值对Entry,最后通过该Entry获取目标值result。

  其中,getMap()方法可以获取当前线程所对应的ThreadLocalMap,其源代码如下:

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

2. set(T value)

// 设置当前线程的线程局部变量的值。public void set(T value) {    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null)        map.set(this, value);    else        createMap(t, value);}

  set方法首先获取当前线程所对应的ThreadLocalMap,如果不为空,则调用ThreadLocalMap的set()方法,key就是当前ThreadLocal,如果不存在,则调用createMap()方法新建一个,其源代码如下:

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

3. initialValue()

// 返回该线程局部变量的初始值。protected T initialValue() {    return null;}

  该方法定义为protected级别且返回为null,很明显是要子类重写来实现它的,所以我们在使用ThreadLocal的时候一般都应该覆盖该方法。该方法不能显示调用,只有在第一次调用get()或者set()方法时才会被执行,并且仅执行1次。

4. remove()

// 将当前线程局部变量的值删除public void remove() {    ThreadLocalMap m = getMap(Thread.currentThread());    if (m != null)        m.remove(this);}

  该方法的目的是减少内存占用,避免出现因为线程迟迟未结束而导致内存泄漏的情况。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

ThreadLocalMap类


  从ThreadLocal的源码中我们可以看到,ThreadLocal的实现比较简单,主要是依赖于ThreadLocalMap这个类,我们有必要好好理解一下后者。

  根据命名就可以看出,ThreadLocalMap,它实际上是一个Map键值对。在其内部使用了Entry的方式来实现key-value的存储:

static class Entry extends WeakReference<ThreadLocal<?>> {    /** The value associated with this ThreadLocal. */    Object value;    Entry(ThreadLocal<?> k, Object v) {        super(k);        value = v;    }}

  在上面的代码中,Entry内的Key就是ThreadLocal,而Value就是线程私有的那个变量。同时,Entry也继承WeakReference,所以说Entry所对应key(ThreadLocal实例)的引用是一个弱引用。

  下面来看一下ThreadLocalMap类中几个核心的方法:

1. set()

    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();    }

  源码的意思简单明了,根据要保存的key到Entry数组中去匹配,如果key已经存在就更新值,否则创建新的entry写入。

  值得注意的是,这里的set()操作和我们在集合Map了解的put()方式有点儿不一样,虽然他们都是key-value结构,不同点在于他们解决散列冲突的方式不同。 集合Map的put()采用的是拉链法,即在每个数组元素的位置,存入链表来解决冲突。而ThreadLocalMap的set()则是采用开放定址法来解决冲突的。

  set()操作除了存储元素外,还有一个很重要的作用,就是replaceStaleEntry()和cleanSomeSlots(),这两个方法可以清除掉key == null 的实例,防止内存泄漏。在set()方法中还有一个变量很重要:threadLocalHashCode,定义如下:

private final int threadLocalHashCode = nextHashCode();

  从名字上面我们可以看出threadLocalHashCode应该是ThreadLocal的散列值,定义为final,表示ThreadLocal一旦创建其散列值就已经确定了,生成过程则是调用nextHashCode():

private static AtomicInteger nextHashCode = new AtomicInteger();private static final int HASH_INCREMENT = 0x61c88647;private static int nextHashCode() {    return nextHashCode.getAndAdd(HASH_INCREMENT);}

  nextHashCode表示分配下一个ThreadLocal实例的threadLocalHashCode的值,HASH_INCREMENT则表示分配两个ThradLocal实例的threadLocalHashCode的增量,从nextHashCode就可以看出他们的定义。

2. getEntry()

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);}

  由于采用了开放定址法,所以当前key的散列值和元素在数组的索引并不是完全对应的,首先取一个探测数(key的散列值),如果所对应的key就是我们要找的元素,则返回,否则调用getEntryAfterMiss()再寻找,源码如下:

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {    Entry[] tab = table;    int len = tab.length;    while (e != null) {        ThreadLocal<?> k = e.get();        if (k == key)            return e;        if (k == null)            expungeStaleEntry(i);        else            i = nextIndex(i, len);        e = tab[i];    }    return null;}

  这里有一个重要的地方,当key == null时,调用了expungeStaleEntry()方法,该方法用于处理key == null,有利于GC回收,能够有效地避免内存泄漏。

ThreadLocal与内存泄漏


  (注:本节参考了博文 http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/)

ThreadLocal实现原理

图:ThreadLocal实现原理

  前面提到过,每个Thread都有一个ThreadLocal.ThreadLocalMap,该map的key为ThreadLocal实例的一个弱引用,我们知道弱引用有利于GC回收。当ThreadLocal的key == null时,GC就会回收这部分空间,但是value却不一定能够被回收。

  如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。

  其实,ThreadLocal类的设计中已经考虑到这种情况,也加上了一些防护措施:在触发ThreadLocal的remove()时会清除线程ThreadLocalMap里key为null的value。

阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 起亚k3怎么样专家点评 起亚k3图片 美国式禁忌3k8在线播放 买起亚k3到底有多少人后悔 海马3 m3u8 瑞风m3 m3u8在线收看 3m m3u8格式转换mp4安卓 m3 mu 瑞风m 特片m3u8网 野猫网m3u8 梦之蓝m3价格 m3u8合并 疯人影院一m3u8 索尼a7m3 疯人影院m3u8 海马m3 江淮m3 m3u8缓冲合并工具 泽艺影城m3u8 3m膜 江淮瑞风m3 m3u8资源网 菱智m3 a7m3 海马m 江淮瑞风m m3u8格式怎么播放 佳能m m3u8mp4 123影院m3u8m3u8 天天磨逼3m8u 天天磨逼3m6u 3m口罩 黑夜影院n3m8 江淮m m3u8合并助手apk