您的位置:首页 > 其它

深入理解ThreadLocal

2018-03-19 10:55 537 查看

深入理解ThreadLocal

ThreadLocal 主要用来提供线程局部变量,与线程同步不同,线程同步是多个线程共享同一个变量,而ThreadLocal是为每一个线程维护一个线程的副本。

ThreadLocal定义了四个方法:

get():返回此线程局部变量的当前线程副本中的值。

initialValue():返回此线程局部变量的当前线程的“初始值”。

remove():移除此线程局部变量当前线程的值。

set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。

案例:

/**
* Created by Administrator on 2018/3/19.
*/
public class ThreadLocalTest {
private static ThreadLocal<Integer> threadlocal = new ThreadLocal<Integer>() {
// 实现initialValue()
public Integer initialValue() {
return 0;
}
};

public int nextValue() {
threadlocal.set(threadlocal.get() + 1);

return threadlocal.get();
}

public static void main(String[] args) {
ThreadLocalTest threadLocalTest = new ThreadLocalTest();

ThreadlocalThread thread1 = new ThreadlocalThread(threadLocalTest);
ThreadlocalThread thread2 = new ThreadlocalThread(threadLocalTest);
ThreadlocalThread thread3 = new ThreadlocalThread(threadLocalTest);

thread1.start();
thread2.start();
thread3.start();
}

private static class ThreadlocalThread extends Thread {
private ThreadLocalTest threadLocalTest;

ThreadlocalThread(ThreadLocalTest test) {
this.threadLocalTest = test;
}

public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " 变量值为 :" + threa
4000
dLocalTest.nextValue());
}
}
}
}


打印结果:



set方法:

//首先获取当前线程,然后调用getMap方法获取属于该线程的map
//如果存在map直接插入value,<K,V> k就是当前Threadlocal的对象引用,v就是插入的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);
}
//getMap方法:
//threadLocals为线程的一个成员变量,指向当前线程的ThreadLocalMap,默认值为null,也就是说第一次调用Threadlocal的get方法时无法获取到map,需要新创建一个ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//createMap方法:
//新建一个ThreadLocalMap,然后将当前线程的threadLocals指向新创建map对象
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}


通过以上源码我们可以发现,ThreadLocalMap存储的键值对是以ThreadLocal对象的引用为key,然后以变量副本为value。

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


get方法较简单,以当前的ThreadLocal的对象引用为key值,从当前线程的ThreadLocalMap中取出该key值对应的Entry,然后得到value值。注意,如果entry不存在则返回ThreadLocal初始化的值,setInitialValue()方法可以被重写,如上例。

ThreadLocalMap结构:

//一个存放变量副本的实体
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//存放所有实体的数组
private Entry[] table;
//初始化方法,注意:firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)作为插入数组的位置索引。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
//getEntry方法,同样是首先key.threadLocalHashCode & (table.length - 1)获取数组位置,然后取出entry,如果不为空或者key值符合条件,将该实体返回,否则调用getEntryAfterMiss方法
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);
}
//getEntryAfterMiss方法:通过分析,该方法实际上是继续查找该key值所对应的entry,为什么需要这样?(可能是因为数组的索引可能存在冲突,在ThreadLocalMap中采用的开放定址法去存储,所以该方法即是开放定址法查找元素)
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;
}
//expungeStaleEntry:该方法主要作用是对key值为空的value值也置空,防止内存泄露。


内存泄露分析:

假如我们将初始化方法重写:

class A
{
}
a=new A();
public A initialValue() {
return a;
}


前面提到每个Thread都有一个ThreadLocal.ThreadLocalMap的map,该map的key为ThreadLocal实例,它为一个弱引用,我们知道弱引用有利于GC回收。当ThreadLocal的key == null时,GC就会回收这部分空间,但是value却不一定能够被回收,因为他还与Current Thread存在一个强引用关系,就比我此例中的a对象引用,当key为空的时候,对象a是无法被GC回收的。

所以,在ThreadLocalMap中的setEntry()、getEntry(),如果遇到key == null的情况,会对value设置为null。当然我们也可以显示调用ThreadLocal的remove()方法进行处理。

最后思考一个问题,每一个线程的ThreadLocalMap中能存在多少个元素,因为是以ThreadLocalMap的对象引用作为key,所以每一次set操作是不是仅仅是覆盖value值,不会往map中添加元素?

感谢:http://cmsblogs.com/?p=2442
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: