ThreadLocal这部分内容在正常开发过程中可能用的很少,但是却是线程操作不可缺少的,尤其是在线程间通过Handler通信是重要的一环,这一篇我就帮着大家一起分析一下ThreadLocal的使用和内部原理。
ThreadLocal是什么
ThreadLocal 是一个关于创建线程局部变量的类。ThreadLocal 可以把一个对象保存在指定的线程中,对象保存后,只能在指定线程中获取保存的数据,对于其他线程来说则无法获取到数据。
ThreadLocal使用
ThreadLocal使用非常简单,通常情况下我们只需要关心内部的set() 和get() 方法即可。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "ThreadLocalTest";
private ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
stringThreadLocal.set("MainThread");
Log.d(TAG, "MainThread's stringThreadLocal=" + stringThreadLocal.get());
new Thread("Thread#1") {
@Override
public void run() {
Log.d(TAG, "Thread#1's stringThreadLocal=" + stringThreadLocal.get());
}
}.start();
}
}
ThreadLocal也可以设置一个默认值,在获取的时候如果有set值就会取set值,没有就取默认值。
private ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<Boolean>(){
@Override
protected Boolean initialValue() {
return false;
}
};
ThreadLocal还有一个remove ,移除保存的对象。
Handler中Looper和ThreadLocal的关系
我们知道在子线程中使用Handler构建消息体Looper必不可少:
class MyThread extends Thread {
@Override
public void run() {
super.run();
Looper.prepare();
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
System.out.println( "threadName--" + Thread.currentThread().getName() + "messageWhat-"+ msg.what );
}
};
Looper.loop();
}
}
在Looper内部定义了一个静态的ThreadLocal,在prepare()过程中专门用来存储Looper实例,并保证一个线程只能有一个Looper实例:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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));
}
接下来在Handler初始化的时候通过ThreadLocal.get 获取到该Looper,拿到Looper的MessageQueue,接着Handler可以执行sendMessage,消息就存储在MessageQueue里面。
最后Looper.looper() 的背后执行就是把MessageQueue里的消息进行遍历然后分发的。
ThreadLocal原理剖析
每一个线程都维护了一个ThreadLocalMap对象,而ThreadLocal存的值就在ThreadLocalMap中(key为ThreadLocal本身,value为当前值),这是是为什么ThreadLocal只能在某个线程中存取其他线程访问不了的原因,一个线程可以拥有多个ThreadLocal(一个线程可以有多个变量存储),但是一个ThreadLocal只能负责一个线程。
ThreadLocalMap是ThreadLocal的静态内部类,ThreadLocal总共七百多行代码,而ThreadLocalMap就占了四五百行,ThreadLocalMap和ThreadLocal是相互依偎的,将二者结合在一起是设计模式的单一职责原则。
ThreadLocal 的 public 方法,只有三个:set、get、remove。
set方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
- 获取当前线程
- 得到当前线程所拥有的ThreadLocalMap
- 存值,key为当前ThreadLocal本身,value是所有存的对象。
ThreadLocalMap是如何set呢?来看一下ThreadLocalMap源码:
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
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);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
ThreadLocalMap存值的背后是Entry数组的存值,数组每一个节点即Entry本身作为key(ThreadLocal),弱引用类型,Entry内部维护了一个变量即用来指向ThreadLocal存的那个值。
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
数组在存的过程中通过Key(ThreadLocal)的hash作为数组的下标索引i找到目标的Entry,Entry已存在则替换value值,如果不存在,则创建一个新Entry。
Entry为什么是弱引用类型
如果Entry不是弱引用类型,实质上就会造成节点的生命周期与线程强绑定,只要线程没有销毁,那么节点在GC分析中一直处于可达状态,没办法被回收。
虽然Entry本身是弱引用,但内部维护的值value是强引用,value引用链不断开则Entry不会回收,一旦value引用链断开了,那么Entry本身也就很容易就回收了。
Entry环形数组
ThreadLocalMap维护了Entry环形数组,Entry数组的环形结构来自set方法中的nextIndex 函数:
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
- 传入index值加一,返回坐标。
- 当index值长度超过数组长度后,会直接返回0,又回到了数组头部,完成了环形结构。
优点就是:
- 长度固定,下标永不越界。
- 空间利用率高,能够大大的节省内存的开销,能够充分的利用数组的空间。
get方法
分析完上边的set方法,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();
}
- 找到当前线程
- 找到线程所拥有的ThreadLocalMap
- 通过Key(当前的ThreadLocal)找到目标Entry
- Entry的value即为get的值
map.getEntry 背后是通过Key(当前的ThreadLocal)的Hash作为Entry数组的某个坐标,找到目标Entry。
remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
主要看背后ThreadLocalMap.remove :
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
remove方法也很简单,通过Key(当前的ThreadLocal)的Hash作为Entry数组的某个坐标,找到目标Entry,直接clear(弱引用内部固有方法)。
Entry数组扩容
通过rehash() 方法进行判断扩容:
private void rehash() {
expungeStaleEntries();
if (size >= threshold - threshold / 4)
resize();
}
扩容条件:数组中含有是实例数大于等于threshold 的四分之三(threshold为数组长度的 三分之二)。
resize方法是实际扩容算法:
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) {
e.value = null;
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
- 扩容的核心是数组的替换和复制。
- 新数组大小为老数组的两倍。
- 复制,算出index值,向扩容数组中存储,如果该节点冲突,向后找到为null的节点,然后存储。
|