相关链接:
http://www.jianshu.com/p/a8fa72e708d3
http://www.jasongj.com/java/threadlocal/
https://juejin.cn/post/6893791473121280013#heading-9
基本概念:
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,
它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
适用场景:
ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景
误解⚠️:好多人错误的以为,ThreadLocal是为了解决多线程访问共享变量的问题,即线程间同步问题
原理:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);//创建当前线程的map(仅第一次调用)
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);//this代表的是ThreadLocal
}
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();//null
}
private T setInitialValue() {
T value = initialValue();//null
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
内存泄漏:
ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。
创建Handler的时候,需要先创建当前线程的Looper,Android默认在启动的时候为我们创建的 主线程的Looper,所以我们可以在主线程直接创建Handler。
但是在子线程创建Handler的时候,我们必须先手动创建Looper,才能创建Handler,否则会抛出一个必须调用Looper.prepare()
的异常信息。
原因:
public Handler(Callback callback, boolean async) { //省略 mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //省略 }
问题:
相同的代码,为什么在主线程创建就可以,在子线程创建就异常???为什么在子线程中,通过Looper.myLooper()
方法获取的就是为空呢?
查看Looper.myLooper()源码,可知道原因:ThreadLocal获取的是“当前线程”的Looper
sThreadLocal.get();
ThreadLocal在什么时候set值的那?--- 在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)); }
ThreadLocal的初始化:--- 在Looper中源码中
// sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
看注释可以知道,sThreadLocal.get()之前,必须Looper.prepare(),否则get到的值为null。
ThreadLocal是什么?
看一下官方的解释:
Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same {@code ThreadLocal} object, but each sees a different value when accessing it, and changes made by one thread do not affect the other threads. The implementation supports {@code null} values.
意思是说:ThreadLocal实现与线程相关的存储。所有线程共享一个ThreadLocal对象,但是不同的线程有自己的值。并且当一个线程的值发生改变之后,不会影响其他的线程的值。
所以这就解释了为什么 在子线程中,如果不Looper.prepare()就直接创建Handler的时候,会抛出异常。
ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据,,数据存储以后,只有在指定线程中可以获取到存储的数据。对于其它线程来说无法获取到数据。
ThreadLocal是一个关于创建线程局部变量的类。通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。
所以今后,如果多线程的每一个线程都有一个相同变量,但是每个线程的变量值只有当前线程自己可以修改,不受其它线程的影响,那么你就可以用ThreadLocal。多线程共享同一个ThreadLocal对象。