您的位置:首页 > 编程语言 > Java开发

【并发】ThreadLocal源码浅析

2016-07-26 16:05 429 查看
最近被安利了ThreadLocal这个东西,认真看看源码是怎么回事。

先上结论:ThreadLocal我理解就是一个线程本地变量,某个字段如果是用ThreadLocal封装起来的话,这个字段在被不同线程访问的时候每个线程会得到一个本线程独有的副本。各个线程读写的都会是自己的副本,互不影响。某个线程死亡时,它拥有的ThreadLocal对象副本会立刻被回收。

最后我还是有一点疑惑,当时安利我的人说,ThreadLocal可以用来做线程间通信,可以提高好几十倍效率。可能我水平不够,还不能理解具体是怎么个用法。

首先看作者对它的描述(类注释):

 * This class provides thread-local variables.  These variables differ from

 * their normal counterparts in that each thread that accesses one (via its

 * <tt>get</tt> or <tt>set</tt> method) has its own, independently initialized

 * copy of the variable.  <tt>ThreadLocal</tt> instances are typically private

 * static fields in classes that wish to associate state with a thread (e.g.,

 * a user ID or Transaction ID).

译:ThreadLocal提供了线程本地变量。这些变量在不同的线程中访问时,都可以获得每个线程独有的一份变量初始化副本。ThreadLocal实例通常是一个类私有静态字段,这个字段我们希望其每个副本与一个线程进行绑定(比如,一个用户ID或者一个事务ID)。

简单理解:ThreadLocal 在使用的时候,形式一般是类静态私有字段。实际上在多线程运行时,每个线程是拥有这个字段的一个独立的副本。

 *

 * <p>For example, the class below generates unique identifiers local to each

 * thread.

 * A thread's id is assigned the first time it invokes <tt>ThreadId.get()</tt>

 * and remains unchanged on subsequent calls.

 * <pre>

译:举个栗子,我们为这个类产生的每个线程生成一个唯一的标识符(ID),一个线程的ID在第一次调用ThreadId.get()的时候生成,并且以后再调用时都不会改变了。以下为例子源码:

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);

// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override protected Integer initialValue() {
return nextId.getAndIncrement();
}
};

// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}


 * </pre>

 * <p>Each thread holds an implicit reference to its copy of a thread-local

 * variable as long as the thread is alive and the <tt>ThreadLocal</tt>

 * instance is accessible; after a thread goes away, all of its copies of

 * thread-local instances are subject to garbage collection (unless other

 * references to these copies exist).

译: 每个线程在它处于生存状态且thread-local变量是可访问的时候,都会拥有ThreadLocal变量的一套副本。当线程死亡,所有的thread-local副本都会被视为垃圾,可进行回收(除非还有其他的引用指向它)。

理解:这里是对ThreadLocal实例的生命周期做一个解释。

接下来看了一下源码,这个ThreadLocal是一个支持泛型的类。

class ThreadLocal<T>

这个泛型 T 既是ThreadLocal对应的对象,可以通过get和set来获取。我本以为ThreadLocal会自己持有这个对象,但仔细看了get和set的源码发现并非如此。ThreadLocal和它对应的对象是用一个Map来存储对应关系的:

/**
* Returns the value in the current thread's copy of this
* thread-local variable.  If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); //这里可以看到,获取对象的时候是去一个Map中,以ThreadLocal为key,得到对应的对象。
if (e != null)
return (T)e.value;
}
return setInitialValue();
}


接下来看看getMap()方法的内容:

/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param  t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}


Thread里的threadLocals的定义如下:

/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal里初始化threadLocals的过程是这样,在放入第一个ThreadLocal时初始化:

/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
* @param map the map to store.
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}


ThreadLocalMap的定义基本和HashMap一样,一个重要的差别是:entry的定义中用了弱引用。

/**
* 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的Entry继承自一个弱引用,这个弱引用指向的是entry的key——一个ThreadLocal对象。弱引用的作用是,当key指向的对象(即ThreadLocal对象)为null时,令这个Entry立刻被回收。这保证了当线程生命周期结束时,ThreadLocal对应的对象可以同步被回收。避免了java虚拟机仍然按根搜索的方式找不可达的对象消耗资源。

这里我有一点疑惑,为什么ThreadLocal里用弱引用不会被gc回收掉呢?毕竟弱引用的定义是:“在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。” 后来想了一下明白了:当线程仍然存活时,除了Thread中的threadLocals这个Map中持有对ThreadLocal的弱引用,线程本身所在的类也持有强引用。当线程死亡时,强引用不存在了,只有弱引用指向的对象会立刻被回收,达到了设计者的目的。

这里即解释了最初注释中关于生命周期的说明:“每个线程在它处于生存状态且thread-local变量是可访问的时候,都会拥有ThreadLocal变量的一套副本。当线程死亡,所有的thread-local副本都会被视为垃圾,可进行回收(除非还有其他的引用指向它)。”

以上。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息