Java 杂谈JAVAjava并发源码解读

Java 之 ThreadLocal 详解

2019-02-05  本文已影响2人  jwfy

ThreadLocal 在java中是充当“线程本地变量”使用的,在每个线程都会创建该对象从而实现线程之间的隔离,具有一定程度上的线程安全(也就是说不是绝对的线程安全,后面会细说这一点)

public T get()   // 获取当前线程的threadlocal存储的数据
public void set(T value)  // 为当前线程的threadlocal设置数据
public void remove()  // 移除threadlocal数据
protected T initialValue()  // 保护方法,构建初始值

接下来依次分析每个方法具体实现细节

public T get() {
    Thread t = Thread.currentThread();
    // 获取当前执行线程t
    ThreadLocalMap map = getMap(t);
    // 这个getMap方法是获取当前线程t持有的ThreadLocalMap对象信息
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        // map的KEY 是 this 也就是当前这个threadLocal对象本身
        // map的VALUE 是 ThreadLocalMap.Entry 一个entry对象(初次看貌似和hashmap的entry类似)
        if (e != null) {
            // 如果当前的value有数据,则直接取出器entry的value对象,返回
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 其余情况全部默认返回初始化值数据,具体看下面的方法
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
    // 返回线程t的threadlocals参数信息
}

private T setInitialValue() {
    T value = initialValue();
    // 默认返回null
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // 再次获取当前线程的threadlocals对象
    if (map != null)
        map.set(this, value);
        // 设置的值是key:this,value:null
    else
        createMap(t, value);
        // 把当前的值塞入到map中
    return value;
}

线程有一个名字叫threadlocals的Map对象,键值对数据是<ThreadLocal,Value>,在获取的时候先看map是否存在,如果存在直接获取键值对信息,否则填充一个默认初始值塞入到线程map中

再来看看ThreadLocalMap 对象的类型

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

ThreadLocalMap 原来和一般意义上的HashMap并没有太多的关系,只是采取了类似的名称而已,而且需要注意到其使用了WeakReference 弱引用,当发生GC操作的时候其会被回收操作

因为threadlocals是线程的一个对象,那么不同的线程map是也是完全隔离的,也正是因为这一点才使得其具备了一定程度的线程安全的特性,不过并不是严格意义上的线程安全。例如下面这个例子说明为什么不是绝对的线程安全

image

如上图所示在一个JVM里面包含了2个线程A和B,在各自线程中的threadlocals对象分别是a和b,age和sex参数都是基础类型,是完全隔离开的,但是却共同引用了同一个Student对象信息,那么就存在A线程修改Student信息 ,然后B获取的Student信息是A修改之后的可能,打破了线程隔离这个特性。

第一点结论:ThreadLocal不具备线程安全的特性,想要拥有线程安全,Value必须是不可变对象

上面有提到ThreadLocalMap中的Entry 是WeakReference<ThreadLocal<?>>弱引用,每次GC操作都会被回收的,使得KEY为NULL,从而ThreadLocalMap存在<NULL, Object> 的对象信息,从而出现内存泄露的问题

为了防止这种问题,所以在使用完threadlocal对象后,需要显示调用remove方法

 public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null)
         // 在没有被GC回收期,从ThreadLocalMap 中主动移除该数据
         m.remove(this);
}
 
 private void remove(ThreadLocal<?> key) {
    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)]) {
        if (e.get() == key) {
            e.clear();
            expungeStaleEntry(i);
             // 如果在tab中找到了对应的键值对,则直接设置其引用为null
            // 也就是使得tab[i] = null ,使得GC能够回收干净,不出现内存泄露的问题
            return;
        }
    }
}

第二点结论:使用ThreadLocal完毕后确保不出现内存泄露的问题,需要显示调用remove方法

总结:ThreadLocal可以用来存储一些线程临时的不可变对象,并不能用来处理线程共享,需要明确注意其组成结构

上一篇下一篇

猜你喜欢

热点阅读