ThreadLocal
是一个线程内部数据存储类,经过他能够在指定的线程中存储数据。存储后,只能在指定的线程中获取到存储的数据,对其余线程来讲没法获取到数据。java
平常使用场景很少,当某些数据是以线程为做用域而且不一样线程具备不一样的数据副本的时候,能够考虑使用ThreadLocal
。 Android
源码的Lopper
、ActivityThread
以及AMS
中都用到了ThreadLocal
。ide
public class ThreadLocalActivity extends AppCompatActivity {
private ThreadLocal<String> name = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread_local);
name.set("小明");
Log.d("ThreadLocalActivity", "Thread:" + Thread.currentThread().getName() + " name:" + name.get());
new Thread("thread1") {
@Override
public void run() {
name.set("小红");
Log.d("ThreadLocalActivity", "Thread:" + Thread.currentThread().getName() + " name:" + name.get());
}
}.start();
new Thread("thread2") {
@Override
public void run() {
Log.d("ThreadLocalActivity", "Thread:" + Thread.currentThread().getName() + " name:" + name.get());
}
}.start();
}
}
复制代码
运行结果:this
D/ThreadLocalActivity: Thread:main name:小明
D/ThreadLocalActivity: Thread:thread1 name:小红
D/ThreadLocalActivity: Thread:thread2 name:null
复制代码
能够看到虽然访问的是同一个ThreadLocal
对象,可是获取到的值倒是不同的。spa
那么为何会形成这样的结果呢?这就须要去看看ThreadLocal
的源码实现,这里的源码版本为API28
。主要看它的get
和set
方法。
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);
}
复制代码
set
方法中首先获取了当前线程对象,而后经过getMap
方法传入当前线程t
获取到一个ThreadLocalMap
,接下来判断这个map
是否为空,不为空就直接将当前ThreadLocal
做为key
,set
方法中传入要保存的值最为value
,存放到map
中;若是map
为空就调用createMap
方法建立一个map
并一样将当前ThreadLocal
和要保存的值做为key
和value
加入到map
中。
接下先看getMap
方法:设计
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
复制代码
getMap
方法比较简单,就是返回从传入的当前线程对象的成员变量threadLocals
。 接着是createMap
方法:code
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
复制代码
createMap
方法也很简单就是new
了一个ThreadLocalMap
并赋给当前线程对象t
中的threadLocals
。 原来这个Map
是存放在Thread
类中的。因而进入Thread
类中查看。
Thread.java
第188-190行:对象
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
复制代码
根据这里的注释能够得知,每一个线程Thread
中都有一个ThreadLocalMap
类型的threadLocals
成员变量来保存数据,经过ThreadLocal
类来进行维护。这样看来咱们每次在不一样线程调用ThreadLocal
的set
方法set
的数据是存在不一样线程的ThreadLocalMap
中的,就像注释说的ThreadLocal
只是起了个维护ThreadLocalMap
的功能。想到是get
方法一样也是到不一样线程的ThreadLocalMap
去取数据。
get
方法:生命周期
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
复制代码
果真,get
方法中一样是先获取当前线程对象,而后在拿着这个对象t
去获取到t
中的ThreadLocalMap
,只要map
不等于null
就调用map.getEntry(this)
方法来获取数据,由于ThreadLocalMap
里使用一个内部类Entry
来存储数据的,因此调用getEntry(this)
方法,传入的key
是当前的ThreadLocal
。这样获取到Entry
类型数据e
,只要e
不为null
,返回e.value
即先前存储的数据。若是获取到的map
为null
又或者根据key
获取Entry
为null
,就调用setInitialValue
方法初始化一个value
返回。
setInitialValue
和initialValue
方法:内存
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);
return value;
}
protected T initialValue() {
return null;
}
复制代码
setInitialValue
方法中首先调用initialValue
方法初始化了一个空value
,以后的操做和set
方法相同,将这个空的value
加入到当前线程的ThreadLocalMap
中去,ThreadLocalMap
为空就建立个Map
,最后返回这个空值。
至此,ThreadLocal
的get
、set
方法就都看过了,也理解了ThreadLocal
能够在多个线程中操做而互不干扰的缘由。可是ThreadLocal
还有一个要注意的地方就是ThreadLocal
使用不当会形成内存泄漏。
内存泄漏的根本缘由是当一个对象已经不须要再使用本该被回收时,另一个正在使用的对象持有它的引用从而致使它不能被回收,致使本该被回收的对象不能被回收而停留在堆内存中。那么ThreadLocal
中是在哪里发生的呢?这就要看到ThreadLocalMap
中存储数据的内部类Entry
。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
复制代码
能够看到这个Entry
类,这里的key
是使用了个弱引用,因此由于使用弱引用这里的key
,ThreadLocal
会在JVM
下次GC
回收时候被回收,而形成了个key
为null
的状况,而外部ThreadLocalMap
是没办法经过null
key
来找到对应value
的。若是当前线程一直在运行,那么线程中的ThreadLocalMap
也就一直存在,而map
中却存在key
已经被回收为null
对应的Entry
和value
却一直存在不会被回收,形成内存的泄漏。
不过,这一点设计者也考虑到了,在get()
、set()
、remove()
方法调用的时候会清除掉线程ThreadLocalMap
中全部Entry
中Key
为null
的Value
,并将整个Entry
设置为null
,这样在下次回收时就能将Entry
和value
回收。
这样看上去好像是由于key
使用了弱引用才致使的内存泄漏,为了解决还特地添加了清除null key
的功能,那么是否是不用弱引用就能够了呢?
很显然不是这样的。设计者使用弱引用是由缘由的。
ThreadLocal
对象已经被回收了可是ThreadLocalMap
还持有ThreadLocal
的强引用,如果没有手动删除,ThreadLocal
不会被回收,一样致使内存泄漏。ThreadLocal
的对象被回收了,由于ThreadLocalMap
持有的是ThreadLocal
的弱引用,即便没有手动删除,ThreadLocal
也会被回收。nullkey
的value
在下一次ThreadLocalMap
调用set
、get
、remove
的时候会被清除。因此,因为ThreadLocalMap
和线程Thread
的生命周期同样长,若是没有手动删除Map
的中的key
,不管使用强引用仍是弱引用实际上都会出现内存泄漏,可是使用弱引用能够多一层保护,null key
在下一次ThreadLocalMap
调用set
、get
、remove
的时候就会被清除。 所以,ThreadLocal
的内存内泄漏的真正缘由并不能说是由于ThreadLocalMap的key
使用了弱引用,而是由于ThreadLocalMap
和线程Thread
的生命周期同样长,没有手动删除Map
的中的key
才会致使内存泄漏。因此解决ThreadLocal
的内存泄漏问题就要每次使用完ThreadLocal
,都要记得调用它的remove()
方法来清除。