ThreadLocal、ThreadLocalMap、Thread关系详细解析

ThreadLocal、ThreadLocalMap、Thread关系详细解析


简单介绍一下这三个类:

1、ThreadLocal类用于存储以线程为作用域的数据,线程之间数据隔离。
2、ThreadLocalMap类是ThreadLocal的静态内部类,通过操作Entry来存储数据。
3、Thread类比较常用,线程类内部维持一个ThreadLocalMap类实例(threadLocals)。

如果大家看过我的上一篇博客 Android 源码解析Handler消息传递机制,应该都清楚Android中的Handler消息机制的Looper是存放在ThreadLocal类中,在写上一篇文章Handler机制的时,由于篇幅的原因我们没有详细的讲解这个ThreadLocal类,我们只是简单的给出ThreadLocal类介绍,那么今天借着周末时间和大家一起学习一下ThreadLocal类,同时介绍一下ThreadLocal、ThreadLocalMap、Thread的关系。

我们在学习handler消息机制的时候给大家介绍ThreadLocal类是用来存储以线程为作用域的变量的,我们Handler中的Looper就存储在ThreadLocal中,也正是因为Looper的创建是依赖当前线程的,一个线程只能有唯一一个Looper,所以Looper使用于存储在ThreadLocal中。ThreadLocal类会根据当前线程存储变量数据的副本,每一个线程之间数据副本互不干扰,实现线程之间的数据隔离。好那么我们看一下下面的这段示例代码:

package zz.com.threadlocaldemo;


/**
 * @author zhengzhong on 2017/11/24
 *         email zheng_zhong@163.com
 * @version V1.0.0
 */

public class Test {


    private static final ThreadLocal<String> threadLocal=new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "我是默认值";
        }
    };

    public static void main(String[] args){
        String hello = "hello";
        threadLocal.set(hello);
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("你好!");
                System.out.println("子线程 :"+threadLocal.get());
            }
        }).start();

        System.out.println("主线程 :"+threadLocal.get());

    }
}


首先我们声明一个ThreadLocal成员变量,指定你存储的数据类型为String,之后我们在主线程中调用其set方法给他赋值为”hello”,之后我们开辟一个子线程,同时在子线程中我们给其赋值为”你好!”,之后我们在主线程和子线程中同时通过threadLocal.get()获取ThreadLocal中的值并打印观察。

子线程 :你好!
主线程 :hello

Process finished with exit code 0


通过观察控制台的输出结果和我们预期的保持一致,不同线程之间threadLocal中变量副本值是不同的。看到这里大家肯定很好奇,ThreadLocal是如何实现这种线程间数据隔离的呢?带好奇心我们去看看它的源码:


首先我们看到public class ThreadLocal<T>{……},ThreadLocal并没有继承Thread所以也不能开辟新的线程。接下来我们分析一下赋值的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);
    }


首先获取当前的线程Thread t = Thread.currentThread();,之后通过当前线程去获取ThreadLocalMapThreadLocalMap map = getMap(t);,getMap(t)方法源码:

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }


通过传入的线程类实例t,获取线程t中的ThreadLocalMap对象,我们继续追踪Thread类,发现Thread的成员变量是包含一个 ThreadLocal.ThreadLocalMap实例。

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;


获取到了ThreadLocalMap实例map后,判断其是否为null,如果不为空,直接调用map.set(this, value);方法,将调用的ThreadLocal对象和传入数据value存储到map中。如果为null,则会调用createMap(t, value);方法。

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


直接初始化ThreadLocalMap,赋值给线程t中的threadLocals。

通过对ThreadLocal的set方法的追踪我们大致了解了ThreadLocal数据存储的过程,每次存储数据都会获取方法被调用的线程,之后将数据和threadlocal对象存储到对应线程的ThreadLocalMap中。这样的存储方式就实现了数据线程之间的隔离。通过不同线程取出的ThreadLocalMap是不同,自然获取到存储的数据也是不同的。

到现在我们通过其set方法,已大致经清楚了其实现方式,好那么接下来我们就用你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方法同样是获取方法调用线程,之后也是通过getMap(t)方法获取ThreadLocalMap 的实例map。如果map!=null直接通过 ThreadLocalMap.Entry e = map.getEntry(this);获取ThreadLocalMap.Entry返回其中的value数据。(ThreadLocalMap类将数据存储到其持有Entry对象的value属性中,Entry是其静态内部类,继承自WeakReference)
如果map==null则会调用setInitialValue()方法:

    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()方法一般只调用一次(当调用ThreadLocal的remove方法后需要再次调用此方法来初始化数据),用于初始化数据,其首先调用initialValue()方法返回默认返回一个null,我们可以通过重写这个方法来指定初始化的默认值。后面的代码和上面相似,就是获取ThreadLocalMap将初始化的value存储到ThreadLocalMap中。

我们指定一下初始化默认值,同时注释掉主线程set方法调用,之后重新运行一下我们最开始的示例代码:

package zz.com.threadlocaldemo;


/**
 * @author zhengzhong on 2017/11/24
 *         email zheng_zhong@163.com
 * @version V1.0.0
 */

public class Test {

    private static final ThreadLocal<String> threadLocal=new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "我是默认值";
        }
    };

    public static void main(String[] args){
        String hello = "hello";
        //threadLocal.set(hello);
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("你好!");
                System.out.println("子线程 :"+threadLocal.get());
            }
        }).start();

        System.out.println("主线程 :"+threadLocal.get());

    }
}
主线程 :我是默认值
子线程 :你好!

Process finished with exit code 0


可以看到我们是可以通过重写initialValue()方法来指定修改初始化的默认值,我们取数据也是通过当前线程获取ThreadLocalMap,之后通过调用的ThreadLocal对象获取对应的ThreadLocalMap.Entry,获取其中的value数据返回。

到这里关于ThreadLocal类,我们介绍的大体完成了,本文只是一个简略的介绍其使用和实现过程,还有很多深层次的东西需要大家自己去探索。


如有疑问欢迎大家留言指正。祝大家生活愉快。

最后欢迎对Android开发感兴趣的老哥一起讨论。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值