阅读本文主要可以解决以下困惑:
- 什么是ThreadLocal,隔离线程的本地变量
- ThreadLocal的数据结构是怎么样的,为什么能实现线程隔离
- ThreadLocal的get和set方法
- ThreadLocal如何实现的线程安全?结合同步锁机制,空间换取时间
- ThreadLocal为什么会出现内存泄漏
- ThreadLocal在Handler中的应用?如何保证Thread和Looper的一一对应关系的
ThreadLocal是什么
ThreadLocal 叫做本地线程变量,意思是说,ThreadLocal 中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal 为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量。
根据这一特点ThreadLocal可以隔离线程
ThreadLocal的数据结构
如何理解ThreadLocal的结构
ThreadLocal的数据结构类似于 HashMap<Thread , ThreadLocalMap<ThreadLocal , T>>
首先我们根据线程Thread作为Key值获取到,每一个线程中的 ThreadLocalMap,而ThreadLocalMap又是一个Map结构,其中ThreadLocal实例作为Key值,存储在该ThreadLocal中的值作为Value
ThreadLocalMap在ThreadLocal中get()方法和set()方法都有可能创建该线程的ThreadLocalMap(最终他们都调用了createMap()方法
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap,这个类没有实现map的接口,就是一个普通的java类,但是实现的类就类似于map的功能,数据用Entry存储,Entry继承于WeakReference,用一个键值对来存储**,键就是ThreadLocal的引用**。每一个线程都有一个ThreadLocalMap的对象,每一个新的线程Thread都会实例化一个ThreadLocalMap并赋予值给成员变量Thread.threadLocals。
public class ThreadLocalTest {
public static void main(String[] args) {
MyRunnable runnable=new MyRunnable();
new Thread(runnable,"线程1").start();
new Thread(runnable,"线程2").start();
}
public static class MyRunnable implements Runnable{
ThreadLocal<String> threadLocal1= ThreadLocal.withInitial(() -> "null");
ThreadLocal threadLocal2= ThreadLocal.withInitial(() -> "null");
@Override
public void run() {
String name = Thread.currentThread().getName();
threadLocal1.set(name+"的threadLocal1");
threadLocal2.set(name+"的threadLocal2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(name + ": "+ threadLocal1.get());
System.out.println(name + ": "+ threadLocal2.get());
}
}
}
线程2: 线程2的threadLocal1
线程1: 线程1的threadLocal1
线程2: 线程2的threadLocal2
线程1: 线程1的threadLocal2
源码分析
ThreadLocal#get()
public T get() {
//1.获取到当前的线程
Thread t = Thread.currentThread();
//2.通过当前线程获取到ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) {
//3.通过当前的ThreadLocal对象
//获取到Entry对象(其中封装着value)
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocal#set()
public void set(T value) {
//1.获取到当前线程
Thread t = Thread.currentThread();
//2.根据线程获取到ThreadLocalMap
//由此可知Thread作为Key,而ThreadLocalMap作为Value
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocal如何做到线程安全
每个线程拥有自己独立的 ThreadLocals变量
(指向ThreadLocalMap对象 )
每当线程 访问 ThreadLocals
变量时,访问的都是各自线程自己的ThreadLocalMap
变量(键 - 值)
ThreadLocalMap
变量的键 key = 唯一 = 当前ThreadLocal实例
ThreadLocal与同步机制的区别
为什么ThreadLocal的键是弱引用
如果使用强引用,当ThreadLocal 对象的引用(强引用)被回收了,ThreadLocalMap本身依然还持有ThreadLocal的强引用,如果没有手动删除这个key ,则ThreadLocal不会被回收,所以只要当前线程不消亡,ThreadLocalMap引用的那些对象就不会被回收, 可以认为这导致Entry内存泄漏。
ThreadLocal的内存泄漏
重点来了,如果一种情况下我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap(因为它是Thread类 的内部属性)生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。
解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。
所以 如同 lock 的操作 最后要执行解锁操作一样,ThreadLocal使用完毕一定记得执行remove 方法,清除当前线程的数值。
如果不remove 当前线程对应的VALUE ,就会一直存在这个值。
使用了线程池,可以达到“线程复用”的效果。但是归还线程之前记得清除ThreadLocalMap,要不然再取出该线程的时候,ThreadLocal变量还会存在。这就不仅仅是内存泄露的问题了,整个业务逻辑都可能会出错。
ThreadLoacal的使用实例
首先讲解以下Handler中四兄弟的对应关系
Looper和MeassgeQueue一一对应,因为消息队列是Looper创建的。这个很好理解
Thread和Looper也是一一对应,那这种对应关系是如何建立的呢?
例如我们在一个子线程中要使用Handler,我们首先会去创建一个Looper,使用Looper.prepare()初始化当前线程的Looper,而在Looper的内部又会创建消息队列。
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare(); //初始化Looper
new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Looper.loop();//开启消息循环
}
})
可以看到源码中prepare方法的实现就是,将Looper保存在了一个ThreadLocal中,因此每一个线程在创建Looper的时候,这种Thread和ThreadLocal的一一对应关系就建立了。而Looper又存放在当前Thread对应的ThreadLooper中,所以Thread与Looper的一一对应关系是借助着ThreadLocal建立的
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));
}
再看一下Looper和MessageQueue的一一对应关系的建立,深入以上代码的new Looper(quitAllowed);
可以看到就是在私有的构造函数中创建了一个MessageQueue对象。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}