我对ThreadLocal的理解

Posted liguangsunls

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我对ThreadLocal的理解相关的知识,希望对你有一定的参考价值。

声明:小弟菜狗一个。对ThreadLocal的描写叙述和理解难免有所偏差

        近期由于须要深入的了解android的handler消息机制而去查看了Looper的源代码。众所周知在主线程中是不须要在程序猿在代码新建一个Looper对象的,由于在主线程创建时它就被创建出来了。所以就好奇它是怎么被创建出来的然后发现它跟ThreadLocal 有关于是便查看了该类的一些资料,但还是不太理解。于是便尝试自己去看一下源代码,然后就有了对ThreadLocal一个又一次的认识。先贴出Looper的代码:

        

   private Looper() {//MessageQueue对象会随Looper对象的创建而创建
        mQueue = new MessageQueue();
        mRun = true;
        mThread = Thread.currentThread();
    }


以下是Looper的代码,从代码中看出sThreadLocal是Looper的成员变量。它被new出来了。

当我第一次看到此代码的时候便产生了一个疑问,印象中不是说ThreadLocal对象都会绑定到一个线程中去的吗,若创建对象那么怎样确定它绑定到哪一个线程中呢(到后来我发现我的这样的想法是不正确的)。于是我便查看了ThreadLocal的代码。首先由于prepare调用到ThreadLocal的set方法。以下先查看下该方法的实现

public class Looper {
    private static final boolean DEBUG = false;
    private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;

    // sThreadLocal.get() will return null unless you‘ve called prepare().
    private static final ThreadLocal sThreadLocal = new ThreadLocal();<span style="font-family: Arial, Helvetica, sans-serif;">   </span>
//该方法事实上就是将一个Looper对象设置进ThreadLocal的一个map中
public static final void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//ThreadLocalMap是ThreadLocal的内部类
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

由ThreadLocal的方法不难看出set方法设置的值最后会与本ThreadLocal对象凑成一个键值对存放到它新建的ThreadLocalMap对象中的。

此时会注意到两个方法getMap(ThreadLocal tl,T t)和createMap(Thread t, T t)。

通过方法名就不难得出此双方法是跟ThreadLocalMap对象的获取和创建有关。

以下先观察ThreadLocal类中createMap方法的代码

 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

通过代码能够知道此方法将一个新建的“键值对”为本ThreadLocal对象和要设置的value值的ThreadLocalMap对象赋值给当前线程的threadLocals变量。接下来查看Thread的代码。

  /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
可见它是Thread的一个成员变量。至此当前线程的threadLocals就不为空并且是不会再被改变(由于从ThreadLocal的set方法中每一次在设置当前threadLocals的值之前都会先推断该对象是否为null)。


通过观察这一系列的代码能够了解到事实上在每个线程中都会有一个ThreadLocal.ThreadLocalMap变量,它与Map有点类似用于存放键值对,只是它的键是ThreadLocal对象,所以一个ThreadLocal对象仅仅能在它所在线程的ThreadLocal.ThreadLocalMap对象对象中存放有自己是key的一个值。事实上此时又会产生一个疑问这种以ThreadLocal

为key的键值对存放到Thread对象中的ThreadLocal.ThreadLocalMap中有什么意义呢?由于当我们失去了ThreadLocal对象之后就不能取出在线程中以该ThreadLocal的相应值。

事实上通过观察Looper的代码不难看出它的ThreadLocal sThreadLocal对象是一个静态变量。因此全部的Looper对象都在“共用”一个ThreadLocal 对象。因此确保了不同Looper的Looper.prepare方法在同一个线程的ThreadLocal.ThreadLocalMap中相应的值是一样的,这确保了一个线程中仅仅有一个Looper对象存放在当前线程的ThreadLocal.ThreadLocalMap中。


下面是Message、Message Queue、Handler、Looper类之间的大概的联系。

#### Handler消息机制

 

> #### Message 消息

Message msg = Message.obtain()
 
Message msg = new Message()//获取Message类的两种方式

> #### Handler 

new Handler(){
handlerMessage(Message msg){
// 处理消息
}
}

> #### Handler的构造方法:

 

public Handler() {
      	...
// 获取looper
        mLooper = Looper.myLooper();//事实上就是在本线程的ThreadLocal.Map中取looper对象
        if (mLooper == null) {
            throw new RuntimeException(
                "Can‘t create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = null;
    }


   public static Looper myLooper() {
        return sThreadLocal.get();
    }

> #### 主线程设置Looper。在ActivityThread类里面

public static final void main(String[] args) {
        ....
// 1.主线程创建Looper 
        Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        Looper.loop();

> #### Looper

public static final void prepare() {//若在调此方法时本线程(非主线程)中不存在looper对象则会创建一个looper对象存放在线程的ThreadLocalMap对象中
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }

// 3、在主线程中设置Looper, new Looper()里面创建了一个MessageQueue

        sThreadLocal.set(new Looper());
    
    public static final void prepareMainLooper() {
       // 2、调用prepare
          prepare();
        setMainLooper(myLooper());
        if (Process.supportsProcesses()) {
            myLooper().mQueue.mQuitAllowed = false;
        }
    }

> #### 主线程调用Looper.loop()方法,主线程就会堵塞,是一个死循环。使用管道(Pipe),是Linux中的一种进程间通信方式,使用了特殊的文件,有两个文件描写叙述符(一个是读取,一个是写入)

 

> #### 应用场景;主进程拿着读取描写叙述符等待读取,没有内容时就堵塞,还有一个进程拿写入描写叙述符去写内容,唤醒主进程,主进程拿着读取描写叙述符读取到内容。继续运行。

 

> #### Handler应用场景:Handler在主线程中创建,Looper会在死循环里等待取消息,1、没取到。就堵塞,2、一旦被子线程唤醒,取到消息。就把Message交给Handler处理。

子线程用Handler去发送消息。拿写入描写叙述符去写消息,唤醒主线程。

 public static final void loop() {
        ...
        while (true) {
// 取消息。假设没有消息。就堵塞
            Message msg = queue.next(); // might block
            ...
 
                msg.target.dispatchMessage(msg);
                ...
}
    }


> #### Handler发送消息代码

public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
        ....
// 把Message的target置为当前发送的Handler,以便Looper取到message后依据target把message分发给正确的Handler
msg.target = this;
// 往队列里面加入Message
            sent = queue.enqueueMessage(msg, uptimeMillis);
        ....
        }


> #### MessageQueue.enqueueMessage 代码

final boolean enqueueMessage(Message msg, long when) {
        ...
            Message p = mMessages;
            if (p == null || when == 0 || when < p.when) {
// 当前发送的message须要立即被处理调。needWake唤醒状态置true
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked; // new head, might need to wake up
            } else {
// 当前发送的message被排队到其它message的后面。needWake唤醒状态置false
                Message prev = null;
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
                msg.next = prev.next;
                prev.next = msg;
                needWake = false; // still waiting on head, no need to wake up
            }
        }
// 是否唤醒主线程
        if (needWake) {
            nativeWake(mPtr);
        }
        return true;
 
> #### Handler.dispatchMessage方法
 
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
// 把Message交给Handler处理
            handleMessage(msg);
        }
    }




       

以上是关于我对ThreadLocal的理解的主要内容,如果未能解决你的问题,请参考以下文章

java中的引用与ThreadLocal

ThreadLocal个人理解梳理

ThreadLocal源码分析理解弱引用和内存泄漏

ThreadLocal源码分析理解弱引用和内存泄漏

《我要进大厂系列 六》-谈谈你对ThreadLocal理解?

ThreadLocal源码分析理解弱引用和内存泄漏