ThreadLocal使用方法&实现原理

Java中的ThreadLocal类容许咱们建立只能被同一个线程读写的变量。所以,若是一段代码含有一个ThreadLocal变量的引用,即便两个线程同时执行这段代码,它们也没法访问到对方的ThreadLocal变量。java

 

建立ThreadLocal变量

private ThreadLocal myThreadLocal = new ThreadLocal();

咱们能够看到,经过这段代码实例化了一个ThreadLocal对象。咱们只须要实例化对象一次,而且也不须要知道它是被哪一个线程实例化。虽然全部的线程都能访问到这个ThreadLocal实例,可是每一个线程却只能访问到本身经过调用ThreadLocal的set()方法设置的值。即便是两个不一样的线程在同一个ThreadLocal对象上设置了不一样的值,他们仍然没法访问到对方的值。并发

 

访问ThreadLocal变量

一旦建立了一个ThreadLocal变量,你能够经过以下代码设置某个须要保存的值:ide

myThreadLocal.set("A thread local value”);

能够经过下面方法读取保存在ThreadLocal变量中的值:函数

String threadLocalValue = (String) myThreadLocal.get();

 

为ThreadLocal指定泛型类型

咱们能够建立一个指定泛型类型的ThreadLocal对象,这样咱们就不须要每次对使用get()方法返回的值做强制类型转换了。下面展现了指定泛型类型的ThreadLocal例子:ui

private ThreadLocal myThreadLocal = new ThreadLocal<String>();

如今咱们只能往ThreadLocal对象中存入String类型的值了。而且咱们从ThreadLocal中获取值的时候也不须要强制类型转换了。this

 

初始化ThreadLocal变量的值

因为在ThreadLocal对象中设置的值只能被设置这个值的线程访问到,线程没法在ThreadLocal对象上使用set()方法保存一个初始值,而且这个初始值能被全部线程访问到。spa

可是咱们能够经过建立一个ThreadLocal的子类而且重写initialValue()方法,来为一个ThreadLocal对象指定一个初始值。就像下面代码展现的那样:线程

private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
    @Override
    protected String initialValue() {
        return "This is the initial value";
    }
};

 

多个线程局部变量 ThreadLocal 的使用

不一样的线程局部变量,好比说声明了n个(n>=2)这样的线程局部变量threadlocal,那么在Thread中的threadlocals中是怎么存储的呢?threadlocalmap中是怎么操做的?code

在ThreadLocal的set函数中,能够看到,其中的map.set(this, value);把当前的threadlocal传入到map中做为key,也就是说,在不一样的线程的threadlocals变量中,都会有一个以你所声明的那个线程局部变量threadlocal做为键的key-value。假设说声明了N个这样的线程局部变量变量,那么在线程的ThreadLocalMap中就会有n个分别以你的线程局部变量做为key的键值对。orm

 

ThreadLocal实现原理

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方法内部咱们看到,首先经过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,而后将变量的值设置到这个ThreadLocalMap对象中,固然若是获取到的ThreadLocalMap对象为空,就经过createMap方法建立。

线程隔离的秘密,就在于ThreadLocalMap这个类。

/**
 * ThreadLocalMap is a customized hash map suitable only for
 * maintaining thread local values. No operations are exported
 * outside of the ThreadLocal class. The class is package private to
 * allow declaration of fields in class Thread.  To help deal with
 * very large and long-lived usages, the hash table entries use
 * WeakReferences for keys. However, since reference queues are not
 * used, stale entries are guaranteed to be removed only when
 * the table starts running out of space.
 */
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(ThreadLocal firstKey, Object firstValue) {
        table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }

    /**
     * Get the entry associated with key.  This method
     * itself handles only the fast path: a direct hit of existing
     * key. It otherwise relays to getEntryAfterMiss.  This is
     * designed to maximize performance for direct hits, in part
     * by making this method readily inlinable.
     *
     * @param key the thread local object
     * @return the entry associated with key, or null if no such
     */
    private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        ThreadLocal.ThreadLocalMap.Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }

}

ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(ThreadLocalMap is a customized hash map suitable only for maintaining thread local values.),每一个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类经过操做每个线程特有的ThreadLocalMap副本,从而实现了变量访问在不一样线程中的隔离。由于每一个线程的变量都是本身特有的,彻底不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。

为了加深理解,咱们接着看上面代码中出现的getMap和createMap方法的实现:

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

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

代码已经说的很是直白,就是获取和设置Thread内的一个叫threadLocals的变量,而这个变量的类型就是ThreadLocalMap,这样进一步验证了上文中的观点:每一个线程都有本身独立的ThreadLocalMap对象。打开java.lang.Thread类的源代码,咱们能获得更直观的证实:

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

那么接下来再看一下ThreadLocal类中的get()方法,代码是这么说的:

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

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

这两个方法的代码告诉咱们,在获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这固然和前面set()方法的代码是相呼应的。

设置到这些线程中的隔离变量,会不会致使内存泄漏呢?ThreadLocalMap对象保存在Thread对象中,当某个线程终止后,存储在其中的线程隔离的变量,也将做为Thread实例的垃圾被回收掉,因此彻底不用担忧内存泄漏的问题。

最后再提一句,ThreadLocal变量的这种隔离策略,也不是任何状况下都能使用的。若是多个线程并发访问的对象实例只容许也只能建立那么一个,那就没有别的办法了,老老实实的使用同步机制来访问吧。

==========END==========

相关文章
相关标签/搜索