JAVA - ThreadLocal 类
ThreadLocal
特性
在当前线程中使用ThreadLocal 存储一个对象时,除非在该线程中重新赋值,否则在该线程中取出来的值,不会因为其他地方调用该变量而改变。
ThreadLocalMap 会在内部Entry.value=传入的对象,保存的是一个副本,而不是变量本身。
代码举例
package threadlocal;
public class ThreadLocalTest {
private static ThreadLocal<Integer> mThreadLocal =
new ThreadLocal<Integer>();
private static ThreadLocal<Boolean> mThreadLocal2 =
new ThreadLocal<Boolean>();
private static int value = 1;
public static void main(String[] args) {
mThreadLocal.set(value);
mThreadLocal2.set(true);
System.out.println(Thread.currentThread().getName() +
" value = " + mThreadLocal.get());
System.out.println(Thread.currentThread().getName() +
" value2 = " + mThreadLocal2.get());
Thread mThread = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName() +
" value = " + mThreadLocal.get());
value++;
mThreadLocal.set(value);
mThreadLocal2.set(false);
System.out.println(Thread.currentThread().getName() +
" value = " + mThreadLocal.get());
System.out.println(Thread.currentThread().getName() +
" value2 = " + mThreadLocal2.get());
}
});
mThread.start();
try {
mThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
" value = " + mThreadLocal.get());
System.out.println(Thread.currentThread().getName() +
" value2 = " + mThreadLocal2.get());
}
}
打印:
main value = 1
main value2 = true
Thread-0 value = null
Thread-0 value = 2
Thread-0 value2 = false
main value = 1
main value2 = true
此处案例用了join(),确保里面的子线程先执行结束。
在进入子线程之前,main线程通过两个ThreadLocal对象存入了1 和 true,
在子线程中,通过两个ThreadLocal 存入 2 和 false,同时取出来,
子线程结束后,main 取的值为先前存入的值,并没有因为子线程使用后值发生改变。
应用案例
Android 中常常使用的Looper
每个线程中创建Looper 时, 可通过Looper.prepare()来创建,并用Looper.loop() 开始循环处理消息。由于每个线程创建Looper 的方式都一样,根据设计思想,一个线程中只需要一个looper即可,那么
如何保证一个线程只有一个looper,如何保证各个looper之间不互相影响?
源码中,在Looper.prepare() 时:
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper
may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
将创建的Looper 保存到ThreadLocal 中,在里面进行创建一个looper副本, 以这样的形式 key:ThreadLocal value: looper 保存。
当Looper.loop()时,会调用myLooper方法,通过ThreadLocal 可将之前存的looper 取出来使用。这样就可以确保looper 还是之前创建的looper。
ThreadLocal 源码分析
ThreadLocal 主要涉及一下几个类:
Thread: 一个线程,提供ThreadLocal.ThreadLocalMap 属性保存数据
ThreadLocal: 提供set get remove 等方法,开发者使用
ThreadLocalMap: ThreadLocal 内部类,真正实现ThreadLocal 的一些方法
ThreadLocalMap.Entry:ThreadLocalMap的内部类,数据的实际存储类
原理图:
ThreadLocal
1. set
public void set(T value) {
//获得当前所在线程
Thread t = Thread.currentThread();
//获得当前所在线程的ThreadLocalMap属性
ThreadLocalMap map = getMap(t);
if (map != null)
//如果ThreadLocalMap不为空,那么直接存储数据(ThreadLocal, 值)
map.set(this, value);
else
//否则创建一个ThreadLocalMap, 直接看createMap(当前线程, 值)
createMap(t, value);
}
ThreadLocal set() 可以分为以下3步:
1. 首先获得当前线程实例,并通过getMap方法,获得其ThreadLocalMap属性。
2. 当前线程有ThreadLocalMap,说明之前已经存过,那么直接用当前的ThreadLocal 作为key,value 作为value,保存到map中。
3. 当前线程没有ThreadLocalMap,则当前线程新建ThreadLocalMap,并将值作为第一组数据存起来。
注意:从这边可以看出,一个线程对应一个ThreadLocalMap;
ThreadLocalMap 存入的是键值对,key: 当前ThreadLocal 对象,value: 传入的对象。
2. get
public T get() {
Thread t = Thread.currentThread();//获取当前线程实例
ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue(); //当前线程没有ThreadLocalMap,则初始化一个
}
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);//给当前线程新建ThreadLocalMap
return value;
}
protected T initialValue() {
return null;
}
从ThreadLocal get 方法来看,主要分为以下2步:
1. 当前线程有ThreadLocalMap,那么直接用当前的ThreadLocal 作为key,取出value.
2. 当前线程没有ThreadLocalMap,则和set中一样给当前线程新建ThreadLocalMap,并存入初始值,其中initialValue 返回的初始值为null。
3. getMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
仅仅返回当前线程的ThreadLocalMap对象。
4. createMap
// 将当前的Thread 中threadLocals 属性赋值为ThreadLocalMap对象
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
新建ThreadLocalMap 的两种方式,构造方法参数不同:
1. 当前ThreadLocal 上下文, value值
2. 直接传入一个ThreadLocalMap, 此方法在Thread 的初始化中使用
5. remove
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
获取当前Thread 的ThreadLocalMap 属性,直接删除当前ThreadLocal为key 的值。
ThreadLocalMap 的实现
//存储数据的类型,为弱引用
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.
* 默认table 数组的长度
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
* 数组,用来存储数据,因为一个线程中可能不止一个ThreadLocal
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
/**
* 设置临界值
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* Increment i modulo len.
* 往后移动
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
* 往前移动
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
解释可见代码
注意注意注意,通过key.threadLocalHashCode & (len - 1) 计算出来的值,该值对应的索引,有概率其他ThreadLocal也会计算得出,此时需要往后移动一位进行保存。下面的查找,删除等都会用到此概念。
1. ThreadLocalMap
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
//创建数组,来存放Entry;
table = new Entry[INITIAL_CAPACITY];
//通过ThreadLocal 的hashcode 以及 长度来定义i值
int i = firstKey.threadLocalHashCode
& (INITIAL_CAPACITY - 1);
//存到Entry数组的第i个位置
table[i] = new Entry(firstKey, firstValue);
size = 1; //size = 1;
//修改临界值 此处设置的为 *2/3,当map中存的值大于这个数时,
//会进行扩容操作,并修改临界值
setThreshold(INITIAL_CAPACITY);
}
//传入ThreadLocalMap对象进行初始化
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
ThreadLocal key = e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
ThreadLocalMap的两种构造方法,
1. 看代码解释
2. 直接将传入的parentMap,保存到table 中
2. set
private void set(ThreadLocal key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); //计算i值
//当i位置 已经存有值了
//1, key 为当前的ThreadLocal,直接赋值
//2, key 的值为空,那么说明该位置没释放掉,那么释放key,
//重新赋值key和value
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value); //i位置为空时,存值
int sz = ++size; //size 增 1
//判断是否超出临界点,如果是,则扩容。给数组扩容,i也跟着改变
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
见代码注释,主要注意2点:
1. 索引的查找,当通过hashcode计算出来的位置有数据值,此时还需要对比key的值是否相同,否则就要继续向后查找空的位置插入数据。
2. 临界点判断,如果超出临界点,那么需要给数组扩容
3. getEntry
//1, 当前i 中的entry 对应的key 正好是当前的threadlocal
//2, e为空,直接返回null; 与1相反,i位置被其他占了,则往后寻找
private Entry getEntry(ThreadLocal key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//当前key 正好是要使用的ThreadLocal
if (e != null && e.get() == key)
return e;
else
//当前key不是 正在使用的ThreadLocal,则需要往后查找
return getEntryAfterMiss(key, i, e);
}
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)
//清理下没用的key
expungeStaleEntry(i);
else
//往后查找 i,看i位置的entry 是否满则条件,
//直到k找到,或者e为空时跳出循环
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
见代码注释,需要注意的是:当key.threadLocalHashCode & (table.length - 1) 计算的索引位置上已经有值,并且key 和当前ThreadLocal 不一致时,那么需要往后查找
1. 找到key 相同,返回value
2. 找到key = null, 清理当前没用的key,可释放空间
3. 直到最后也没找到,返回null
4. remove
private void remove(ThreadLocal key) {
Entry[] tab = table;
int len = tab.length;
//计算key 应当存着的i值
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//如果i位置上有值,并且key就是需要删除的,则清除数据
//否则更改i的值,往后查找,当找到key后,删除数据
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
见代码注释,注意点和get 方法相同,索引的位置
5. other
//替换i或i后面位置上,entry不为空但key为空的值
private void replaceStaleEntry(ThreadLocal key,
Object value,int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
//往前找到key 为null的值
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
//往后继续查找,如果能找到key 那么直接赋值
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal k = e.get();
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry
(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
//当找不到key时,将当前位置给赋值了
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
//去除多余占用空间的entry
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
//清理staleslot位置上的值,并将后面的值重新梳理一遍
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot 将当前位置给清空,数量-1
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null 从staleshlot位置往后查找
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal k = e.get();
//当发现有key为null时, 将该位置信息删除
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
//如果key 不为空,看key生成的h值 和 当前的i值是否一致
int h = k.threadLocalHashCode & (len - 1);
//查看生成的h位置上是否有值,如果有,那么往后加1,
//重新给h定值,并将当前entry 赋值到h位置上
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
//清除i位置到size 之间的entry不为空 但 key为空的数据
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;
}
//梳理一遍,清除没用的key 以及 扩容
private void rehash() {
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}
//给存储数据的Enpty 数组扩容,涉及到数组的重新赋值
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal k = e.get();
if (k == null) { //key 为空时,清理其对应的值也为空
e.value = null; // Help the GC
} else {
//如果在对应的h位置上已经有值了,那么往后看有没有空的地方
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
//清理所有的位置上key 为空的值
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
见代码注释,
replaceStaleEntry // 替换i或i后面位置上,entry不为空但key为空的值
expungeStaleEntry //清理staleslot位置上的值,并将后面的值重新梳理一遍
cleanSomeSlots //清除i位置到size 之间的entry不为空 但 key为空的数据
rehash //梳理一遍,清除没用的key 以及 扩容
resize //给存储数据的Enpty 数组扩容,涉及到数组的重新赋值
expungeStaleEntries //清理所有的位置上key 为空的值
~看到新问题会继续补充
下一篇: 26. for 空行的秘密