ThreadLocal说明
ThreadLocal是一个线程内部的数据存储类,使用它来保存数据,只有当前的线程才可以访问,其他线程无法访问到其存储的数据,这个在某些场景下是非常有用的。比如:Android 里面Looper,Handler机制,对于Handler来说,要获取到线程里面的Looper,就必须使用ThreadLocal来存储,否则无法拿到指定的Looper,这个在源码中也有所体现:
//1 Handler.java
public Handler(Callback callback, boolean async) {
...略去
//获取looper
mLooper = Looper.myLooper();
...略去
}
//2 Looper.java
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));
}
public static Looper myLooper() {
//获取存储的Looper
return sThreadLocal.get();
}
ThreadLocal 使用小demo
public class MainActivity extends AppCompatActivity {
@BindView(R.id.btn_text)
Button mBtnText;
private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
Logger.init("liao").logLevel(LogLevel.FULL);
}
@OnClick(R.id.btn_text)
public void textClick() {
mBooleanThreadLocal.set(true);
Logger.d("[Thread#main]mBooleanThreadLocal= %s", mBooleanThreadLocal.get());
new Thread("Thread#1") {
@Override
public void run() {
mBooleanThreadLocal.set(false);
Logger.d("[Thread#1]mBooleanThreadLocal= %s", mBooleanThreadLocal.get());
}
}.start();
new Thread("Thread#2") {
@Override
public void run() {
Logger.d("[Thread#2]mBooleanThreadLocal= %s", mBooleanThreadLocal.get());
}
}.start();
}
}
输出结果:
可以看到,
第一个在主线程设置为true,所以获取到的为true;
第二个我们设置了为false,所以获取到的为false;
第三个我们没有赋值,所以获取到的为null。
从上面的日志我们可以看出,虽然我们访问的是同一个对象,但是获取到的值是不一样的,这个就是ThreadLocal的神奇之处,在某些场景下我们可以实现很复杂的功能。
ThreadLocal 原理
ThreadLocal是一个泛型类:
public class ThreadLocal<T>
既然具有存储数据的功能,那么就会有get,set等方法,所以我们只需弄懂这些方法,那么ThreadLocal的原理也就明白了。
首先我们看下Thread类:
//1. Thread.java
public class Thread implements Runnable {
...
//专门用于存储线程的ThreadLocal的数据
ThreadLocal.Values localValues;
...
}
再来看下ThreadLocal类的set方法:
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
//为null,则初始化一个Values值
values = initializeValues(currentThread);
}
//存储ThreadLocal值
values.put(this, value);
}
下面我们来看Values类,此类提供存储ThreadLocal值的设置:
static class Values {
...
//存储ThreadLocal值
private Object[] table;
...
// 具体存储ThreadLocal算法:
//ThreadLocal的值在table数组中的存储位置总是为
//ThreadLocal的reference字段所标识的对象的下一个位置,
//比如ThreadLocal的reference对象在table数组的索引为
//index,那么ThreadLocal的值在table数组中的索引就是index+1。
//所以最终ThreadLocal的值将会被存储在table数组中:table[index + 1] = value。
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
}
最后再来看下ThreaLocal的get方法:
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
//获取存储ThreadLocal数据值
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
//获取值ThreadLoad索引的下一个
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
最后附上一张类图: