threadLocal能够保证变量在每个线程中单独存在,各个线程互相不会影响。
通常我们这么写threadLocal变量。
private ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
public Integer initialValue() {
return 0;
}
};
这给我们造成一种误解,认为线程的变量信息都存在在了ThreadLocal类中。其实不是酱紫的。
---ThreadLocal
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;}
--Thread
ThreadLocal.ThreadLocalMap threadLocals = null;
从ThreadLcoal的getMap方法可以看出这个维护了线程区分的变量,其实是放在了线程类Thread当中。
大体描述:线程类包含一个ThradLocalMap变量,这样如果有三个线程就有三个ThrdLocalMap.ThrdLocalMap类中包含一个Entry数组Table[Entry],而Entry类型也是ThreadLcoal,只不过是弱引用WeakReference。线程变量放置原理就是先通过hash获得位置,如果该位置有数据就往后继续找。如果找到与其key相同的Entry,则更新value。如果找到空位置,则创建Entry,把数据放置进去。
代码:
1、初始化变量过程、获取ThreadLocal类的变量
初始化过程是在线程get数据的时候才走的,代码如下。
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的threadLocals ThreadLocalMap map = getMap(t);
if (map != null) {
//根据当前变量在当前线程的hashCode或得到该Entry信息
ThreadLocalMap.Entry e = map.getEntry(this);
//如果获得不为空,则返回值
if (e != null)
return (T)e.value;
}
//如果获得为空,则走初始化方法获得首个变量的值。
return setInitialValue();
}
2、设置ThreadLocal类的变量
public void set(T value) {
//获得当前线程
Thread t = Thread.currentThread();
//获得当前线程的ThreadLocalMap变量
ThreadLocalMap map = getMap(t);
//如果或得到,则更新该值为新值。(1)
if (map != null)
map.set(this, value);
else
//如果没有或得到,则在map中创建新的Entry放置该值。(2)
createMap(t, value);
}
(1)、(2)的实现代码如下:
(1)
private void set(ThreadLocal key, Object value) {
//注释中强调没有通过get(key)方法去获得相应的值,是因为大多数时候set方法是要新创建一个Entry。而这种情况用get方法会引入异常。//获得ThradLocalMap的所有Entry
Entry[] tab = table;
//获得ThradLocalMap中table 的长度
int len = tab.length;
//根据key的哈希code去获得该数据应该在的位置(不一定找到)
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//获得Entry的ThradLocal信息
ThreadLocal k = e.get();
//如果该值和key相等,则更新该值。
if (k == key) {
e.value = value;
return;
}
//如果改位置为空则处理过期数据
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//在改位置新生成一个Entry实例。
tab[i] = new Entry(key, value);//数组的长度+1
int sz = ++size;
//如果该过程没有清除过期数据的操作(1-1),并且长度大于了预制,那么会调用rehash方法(1-2)
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
(1-1):清除过期数据的操作
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {//从位置i开始往后走
i = nextIndex(i, len);
Entry e = tab[i];
//该位置数据的值为空
if (e != null && e.get() == null) {
n = len;
//清除标示为为true
removed = true;
/开启清除过期实体的方法(1-1-1)
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
(1-1-1)清除过程:
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
// 清除过期位置的entry数据
tab[staleSlot].value = null;
tab[staleSlot] = null;
//有数值的个数-1
size--;
// Rehash until we encounter null
//再次hash知道我们碰到一个值为null的Entry
Entry e;
int i;
//找到从位置i开始不为空的Entry 放置到e中。
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
//获得该值的ThreadLocal信息
ThreadLocal k = e.get();
//如果为空,则清除
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
//如果不为空,则获得该Entry的hashCode,获得一个位置
int h = k.threadLocalHashCode & (len - 1);
//如果这个位置和数据为空的位置不相等
if (h != i) {
//则清空数据为空的实体变量
tab[i] = null;
//找到h之后第一个数值为空的Entry,将e放置到该位置
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
//返回为空的位置信息
return i;
}
(1-2)、rehash
private void rehash() {
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
//变量table
for (int j = 0; j < len; j++) {
Entry e = tab[j];
//如果找到非空实体,而值为空,则执行清除过期实体的操作。
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
}
}
(2)、创为该线程生成新的threadLocals变量。这个变量很有意思,他把ThradLocalMap实体为变量传给了thread的 threadLocals。这也就是巧妙之处,数据放置到了
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
//初始化一个table
table = new Entry[INITIAL_CAPACITY];
//根据该ThradLocalMap实体的hashCode获得位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//生成基于ThradLocalMap实体的hashCode为key、firstValue为值的Entry信息。
table[i] = new Entry(firstKey, firstValue);
size = 1; setThreshold(INITIAL_CAPACITY);
}
//总容量的三分之二为阈值。
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
最后附上一个简单的图,供大家理解下。
测试代码也贴上来
/**
* Created by user on 2015/12/17.
*/
public class TestClient extends Thread {
private ThreadLocalTest sn;
public TestClient(ThreadLocalTest sn,String name ) {
super(name);
this.sn = sn;
}
public void run() {
for (int i = 0; i < 30; i++) {
// 每个线程打出3个序列值
System.out.println("thread[" + Thread.currentThread().getName()
+ "] sn[" + sn.getNextNum() + "]");
System.out.println("thread[" + Thread.currentThread().getName()
+ "] sn[" + sn.getNextStr() + "]");
System.out.println("thread[" + Thread.currentThread().getName()
+ "] sn[" + sn.getIntNextValue() + "]");
}
}
}
/**
* Created by user on 2015/12/17.
*/
public class ThreadLocalTest {
private ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
public Integer initialValue() {
return 0;
}
};
private ThreadLocal<String> seqString = new ThreadLocal<String>(){
public String initialValue() {
return "app";
}
};
private ThreadLocal<Double> seqDouble = new ThreadLocal<Double>(){
public Double initialValue() {
return 1.0;
}
};
public int getNextNum() {
seqNum.set(seqNum.get() + 1);
return seqNum.get();
}
public String getNextStr(){
seqString.set(seqString.get()+"A");
return seqString.get();
}
public Double getIntNextValue(){
seqDouble.set(seqDouble.get()+2.0);
return seqDouble.get();
}
public static void main(String[] args){
ThreadLocalTest sn = new ThreadLocalTest();
TestClient t1 = new TestClient(sn,"线程1");
TestClient t2 = new TestClient(sn,"线程2");
TestClient t3 = new TestClient(sn,"线程3");
TestClient t5 = new TestClient(sn,"线程5");
TestClient t6 = new TestClient(sn,"线程6");
t1.start();
t2.start();
t3.start();
t5.start();
t6.start();
}
}