您的位置:首页 > 其它

ThreadLocal源码分析与使用场景

2017-05-20 18:23 531 查看

一、概述

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。

ThreadLocal实现的思路:Thread类中持有一个ThreadLocalMap的引用,用于存储每一个线程的变量副本,这个map在使用ThreadLocal变量时候被延迟创建和初始化,并在线程退出时候被释放。Map中元素的键为this所指向的ThreadLocal实例(并不是所以为的线程对象),而值对应需要使用的变量的副本。下面上源码

二、源码分析

1. 关于ThreadLocalMap属于Thread还是ThreadLocal之争

public
class Thread implements Runnable {

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

}


以上可以看到Thread类中持有的ThreadLocalMap ();同时,注释中指出了这个变量由ThreadLocal类来维护,下面就是今天的主角ThreadLocal;所以,这个map是被线程所持有的,但是其初始化和维护都是在ThreadLocal中。

2. ThreadLocal中的四个方法

2.1 get方法 及其调用的方法

public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
/**
* 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;
}

/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
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;
}

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


可以看到,threadLocals最初是从线程t获取。若尚未初始化,则调用 setInitialValue()

2.2 set方法

/**
* Sets the current thread's copy of this thread-local variable
* to the specified value.  Most subclasses will have no need to
* ove
d34d
rride this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
*        this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}


这里就可以看到,map的key并非原以为的当前线程对象,而是this,这时候this的指向应该是当前的ThreadLocal对象

2.3 remove方法

/**
* Removes the current thread's value for this thread-local
* variable.  If this thread-local variable is subsequently
* {@linkplain #get read} by the current thread, its value will be
* reinitialized by invoking its {@link #initialValue} method,
* unless its value is {@linkplain #set set} by the current thread
* in the interim.  This may result in multiple invocations of the
* <tt>initialValue</tt> method in the current thread.
*
* @since 1.5
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}


无F可说

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

/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;

/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
.
.
.


可以看到,ThreadLocalMap是ThreadLocal的一个静态内部类,其中又有一个继承了WeakReference的Entity,是一个定制化的hashMap.

三、show the code

public class LeThreadLocal extends Thread {

private static ThreadLocalValue threadLocalValue;
ThreadLocal<ThreadLocalValue> threadLocal = new ThreadLocal<>();

public static void main(String[] args) {

for (int i = 0; i < 5; i++) {
LeThreadLocal t = new LeThreadLocal();
t.start();
}

}

@Override
public void run() {
for (int j = 0; j < 5; j++) {
getThreadLocalValue().setIndex(getThreadLocalValue().getIndex() + 1);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
getThreadLocalValue().setS(Thread.currentThread().getName());
System.out.println(getThreadLocalValue());
}
}

public ThreadLocalValue getThreadLocalValue() {
if (threadLocal.get() == null) {
threadLocal.set(new ThreadLocalValue(null, 0));
getThreadLocalValue();
}
return threadLocal.get();

}
}

class ThreadLocalValue {

private String s;
private int index;

public String getS() {
return s;
}

public void setS(String s) {
this.s = s;
}

public int getIndex() {

return index;
}

public void setIndex(int index) {
this.index = index;
}

public ThreadLocalValue(String s, int index) {
this.s = s;
this.index = index;
}

@Override
public String toString() {
return "ThreadLocalValue{" +
"s='" + s + '\'' +
", index=" + index +
'}';
}
}


测试输出

ThreadLocalValue{s='Thread-4', index=1}
ThreadLocalValue{s='Thread-0', index=1}
ThreadLocalValue{s='Thread-1', index=1}
ThreadLocalValue{s='Thread-2', index=1}
ThreadLocalValue{s='Thread-3', index=1}
ThreadLocalValue{s='Thread-4', index=2}
ThreadLocalValue{s='Thread-0', index=2}
ThreadLocalValue{s='Thread-3', index=2}
ThreadLocalValue{s='Thread-2', index=2}
ThreadLocalValue{s='Thread-1', index=2}
ThreadLocalValue{s='Thread-1', index=3}
ThreadLocalValue{s='Thread-4', index=3}
ThreadLocalValue{s='Thread-0', index=3}
ThreadLocalValue{s='Thread-3', index=3}
ThreadLocalValue{s='Thread-2', index=3}
ThreadLocalValue{s='Thread-1', index=4}
ThreadLocalValue{s='Thread-3', index=4}
ThreadLocalValue{s='Thread-4', index=4}
ThreadLocalValue{s='Thread-0', index=4}
ThreadLocalValue{s='Thread-2', index=4}
ThreadLocalValue{s='Thread-1', index=5}

ThreadLocalValue{s='Thread-0', index=5}
ThreadLocalValue{s='Thread-3', index=5}
ThreadLocalValue{s='Thread-4', index=5}
ThreadLocalValue{s='Thread-2', index=5}


分析: ThreadLocal变量的使用不同于普通变量,对于本例中的静态成员变量而言,应该在每次使用该变量的时候都首先在ThreadLocal中获取这个变量,而不是直接使用其引用进行操作。这也充分印证了,ThreadLocal为每个线程维护了一个副本,而不是多线程操作这个共享的变量。也就是说使用ThreadLocal类去维护一个变量,并不是为了让多线程共同去的效果叠加的操作这个变量,而是互不干涉的使用这个变量的副本,他们的效果不能叠加。

例子中的使用方式是,在可执行(Runnable)的类中维护一个ThreadLocal实例,并在初次使用需要隔离的变量时候,调用threadLocal.set(value)方法,并在后续操作和使用隔离变量value的时候都先在threadLocal中get,而不是直接操作变量的引用。

在Session维护的场景中也经常用到ThreadLocal,其使用方式是将比较宝贵的连接或者会话资源保存在threadLocal中,在这个线程的声明周期内都使用这个session;

易错点:

即便是ThreadLocal中变量,在使用时要先在threadLocal中get,而不是直接操作器引用,尤其是在类静态变量的使用上;要时刻想着去拿副本

在ThreadLocalMap中,key是当前threadLocal的实例,所以一个ThreadLocal只是维护了一个变量,如果有两个变量,那么需要用两个ThreadLocal的实例。

四、关于内存泄漏



(参考来源: http://blog.csdn.net/wudiyong22/article/details/52141608

ThreadLocal的实现是这样的:每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal 实例本身,value 是真正需要存储的Object。也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。值得注意的是图中的虚线,表示ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。

ThreadLocal为什么会内存泄漏

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。

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

防止措施:

在每次使用完ThreadLocal,都调用它的remove()方法,清除数据
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: