ThreadLocal的使用
ThreadLocal其实就是给线程设置了一个线程中的局部变量,用来隔离线程,保证数据安全。
public class ThreadLocalDemo {
static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
Thread[] thread = new Thread[5];
for (int i = 0 ; i < 5 ; i++){
thread[i] = new Thread(() -> {
int num = threadLocal.get();
threadLocal.set(num+=5);
System.out.println(Thread.currentThread().getName() + "-" +num);
});
}
for (int i = 0 ;i < 5 ;i++){
thread[i].start();
}
}
}
输出结果
ThreadLocal的API
- T initialValue():该方法用来设置ThreadLocal的初始值,方法在调用get方法的时候会第一次调用,但是如果一开始就调用set方法,则该方法不会被调用。通常该方法只会被调用一次,除非手动调用了remove方法之后又调用get方法,这种情况下,get方法中还是会调用initialValue方法。该方法是protected类型的。很显然是建议在子类重写该方法的,所以通常该方法都会以匿名内部类的形式被方法,以指定初始值。
- T get():用来获取与当前线程关联的ThreadLocal的值,如果当前线程没有该ThreadLocal的值,则调用initialValue方法获取初始值返回。
- void set(T value):用来设置当前线程的该ThreadLocal的值。
- void remove():用来将当前线程的ThreadLocal绑定的值删除,在某些情况下需要手动调用该函数,防止内存泄漏。
ThreadLocal的源码
get()方法
/**
* 用来获取与当前线程关联的ThreadLocal的值,如果当前线程没有该ThreadLocal的值,
* 则调用initialValue方法获取初始值返回。
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 获取ThreadLocalMap中的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果ThreadLocalMap=null,即第一次调用,则返回initialValue()设置的初始值
return setInitialValue();
}
- 如果第一次调用get(),则返回initialValue()设置的初始值
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// ThreadLocalMap初始化
createMap(t, value);
return value;
}
// 初始化线程t里面的ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// 初始化ThreadLocalMap,key是ThreadLocal
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始化Entry数组,key和value会存入到Entry中
table = new Entry[INITIAL_CAPACITY];
// 生成数组下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
- ThreadLocalMap不为null,即不是第一次调用,则根据传入的ThreadLocal拿到数组下标,然后拿到value值
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);
}
set()方法
// 用来设置当前线程的该ThreadLocal的值。
public void set(T value) {
Thread t = Thread.currentThread();
// 每一个线程中都有一个ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 第一次的话需要创建一个ThreadLocalMap
createMap(t, value);
}
- ThreadLocalMap如果为空,需要初始化ThreadLocalMap,和get()方法中的初始化一样
// 初始化线程t里面的ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// 初始化ThreadLocalMap,key是ThreadLocal
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始化Entry数组,key和value会存入到Entry中
table = new Entry[INITIAL_CAPACITY];
// 生成数组下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
- ThreadLocalMap不为空,则进行赋值
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 线性探测,解决hash冲突的策略
// 写入:找到发生冲突最近的空闲单元
// 查找:从发生冲突的位置往后查找
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 正常情况,找到Entry然后赋值
if (k == key) {
e.value = value;
return;
}
// 因为Entry是弱引用,所有k值可能为null(内存泄漏问题)
if (k == null) {
// 替换脏的Entry
replaceStaleEntry(key, value, i);
return;
}
}
// 此下标i没有Entry
tab[i] = new Entry(key, value);
int sz = ++size;
// 扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
- 如果Entry中的key被回收,即key==null,需要清除掉脏数据
// 替换脏的Entry
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
// 向前查找和向后查找是因为1.如果当前的entry[i]的key=null,附近的节点也可能有脏的Entry
// 2.为了解决hash冲突
// 向前查找遇到脏的Entry,修改slotToExpunge的值,Entry为null时截止
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
// 向后查找,Entry为null时截止
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 如果发现后面有k和我们传进来的key相等的Entry,直接替换value
if (k == key) {
e.value = value;
// 把找到的这个Entry和我们的脏的Entry的位置互换
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot)
slotToExpunge = i;
// expungeStaleEntry()定点清除脏数据并继续向下探测找到新的脏数据并清除,以null结束
// cleanSomeSlots()以上一个null为节点继续向下探测,清除所有脏数据
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
- expungeStaleEntry()定点清除
// 定点清除脏数据并继续向下探测找到新的脏数据并清除,以null结束
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
Entry e;
int i;
// 继续往下清除
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// k == null直接清除
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// h和i不等,清除当前下标i的值,然后把下标h的值找一个为null的地址存放
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
// 返回最近的为null的下标
return i;
}
- cleanSomeSlots()清除所有脏数据
// 以上一个null为节点继续向下探测,清除所有脏数据
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
// 继续向下探测
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}