ThreadLocal的使用和实现原理

概述

ThreadLocal是一个将指定值储存到指定线程的类。简单的讲ThreadLocal可以指定储存数据类型,然后在不同线程中设置某个值,这个值在其他线程是不可以获取到的,只能在本线程才能获取到。平常中很少使用到ThreadLocal,但在也不少见到,比如Looper类中就有使用到它。

使用

ThreadLocal的使用不难,就是先创建ThreadLocal对象并指定要存储的类型,分别在各个线程中存放指定的值即可。

 private  ThreadLocal<String>mThreadLocal=new ThreadLocal<>();//创建ThreadLocal对象并指定存储String类型
  mThreadLocal.set("main");//在主线程中存储"main"字符串

        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                mThreadLocal.set("t1");//在t1线程中存储"t1"字符串
                Log.i(TAG, "t1-run:"+mThreadLocal.get());
            }
        });

        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {在t2线程中不存储任何值,直接获取
                Log.i(TAG, "t2-run:"+mThreadLocal.get());
            }
        });
        t1.start();
        t2.start();
        Log.i(TAG, "main-run:"+mThreadLocal.get());

最后日志输出的结果为:

com.sendi.threadlocaltest I/MainActivity: main-run:main
com.sendi.threadlocaltest I/MainActivity: t2-run:null
com.sendi.threadlocaltest I/MainActivity: t1-run:t1

根据输出的结果,很明显看出不同线程只能获取到各自存储的值,没有存储值则获取到null。它有点类似于HashMap,但实际内部实现却不同。
以下是它内部实现原理的解析。
在看源码之前,先上一张关系图
这里写图片描述

实现原理

  • 首先从它是set方法入手
    public void set(T value) {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//根据当前线程去获取到一个ThreadLocalMap对象
        if (map != null)
            map.set(this, value);//将value设置进ThreadLocalMap中
        else
            createMap(t, value);//创建一个ThreadLocalMap对象,并将value存储到里边去
    }

createMap(Thread, T)方法源码如下:它创建一个ThreadLocalMap对象,并将该对象赋值给当前线程的threadLocals成员

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

getMap(Thread)方法比较简单,就是根据当前线程去获取到对应的ThreadLocalMap对象。

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

接下来看看ThreadLocalMap的set方法。

由于关注的是ThreadLocal的对数据的存储和获取,所以这里我们只看 ThreadLocalMap的set和get方法:

  • ThreadLocalMap的set(ThreadLocal< ? > key, Object value)方法实现
private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            //通过ThreadLocal去获取到在数组中的索引
            int i = key.threadLocalHashCode & (len-1);
            //遍历Entry数组
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //如果遍历到的Entry对象中的引用ThreadLocal与传进来的key相同时,
                //则将传进来的value赋值给Entry中的value
                if (k == key) {
                    e.value = value;
                    return;
                }
                //如果遍历到的Entry对象中的引用ThreadLocal为null时,
                //则调用replaceStaleEntry()方法,此方法实现的大概思路在下边说明
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //如果以上两种情况都没有发生,则ThreadLocal和value封装成Entry并添加进Entry数组中
            tab[i] = new Entry(key, value);
            int sz = ++size;//长度加1
            //判断是否需要扩容
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
  • replaceStaleEntry方法,里边大概逻辑是:
    接着i索引往下遍历找到一个存储的key与传进来的key相同的Entry对象,然后替换传进来的key和value,否则把传进来的ThreadLocal和value封装成Entry并添加进Entry数组中,最后决定要清除哪一些旧的Entry。

小总结

  1. ThreadLocal的set方法里边的逻辑就是根据所在线程获取一个ThreadLocalMap对象,
  2. 如果对象不为null,则调用ThreadLocalMap的set方法,
  3. 否则创建一个ThreadLocalMap对象并赋值给线程的threadLocals属性。
  4. 在ThreadLocalMap的set方法中,遍历Entry数组,如果遍历到的Entry对象中的ThreadLocal对象与set方法传进来的key相同时,则直接将传进来的value替换Entry中的value值
  5. 如果Entry对象中的ThreadLocal对象为null,则继续从i向后遍历Entry数组,如果set方法传进来的key存在于后续的Entry对象(后面用A对象表示)中,则将A对象的value换为set方法传进来的value,然后将Entry[i]与A对象互换。

到这里可以知道值其实不是存储在ThreadLocal中,而是存储在ThreadLocalMap的Entry数组中的某个Entry中。实际上ThreadLocal只是作为Entry中的key。

  • 接下来是ThreadLocal的get方法
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//根据所在线程获取ThreadLocalMap 对象
        if (map != null) {
        //根据ThreadLocal获取对应的Entry对象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                //获取Entry对象中的Value
                T result = (T)e.value;
                return result;
            }
        }
        //setInitialValue方法里边的逻辑跟set方法差不多,
        //只是将Value初始化为null在存储进去;然后返回一个null
        return setInitialValue();
    }
  • ThreadLocalMap 中的getEntry方法的实现
 private Entry getEntry(ThreadLocal<?> key) {
             //根据当前的ThreadLocal对象来获取对应的索引
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
            //当ThreadLocal对应的Entry对象不存在时调用此方法
            //此方法的逻辑在下边说明
                return getEntryAfterMiss(key, i, e);
        }
  • getEntryAfterMiss(key, i, e)的逻辑:继续向下寻找一个与传进来的ThreadLocal对象相同的key所在的Entry,同时对key为null的Entry进行清除。

小总结

  1. ThreadLocal的get方法中,通过所在线程去获取ThreadLocalMap对象
  2. 通过ThreadLocalMap对象获取Entry对象
  3. ThreadLocalMap在获取Entry对象的过程中,同时对存放ThreadLocal为null的Entry进行清除

最后是ThreadLocalMap中的结构:

ThreadLocalMap是ThreadLocal的一个静态内部类

  static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {

            Object value;//真正把值存放在此

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        //默认Entry数组容量
        private static final int INITIAL_CAPACITY = 16;

        //Entry数组
        private Entry[] table;

        //当前元素长度
        private int size = 0;

       //闸值,当当前元素长度大于等于此值时,会进行扩容
       //它等于 容量*2/3
        private int threshold; 
}

总结

  • 不同线程中存放着不同的ThreadLocalMap对象
  • ThreadLocal并没有存放值,它只是充当key的角色,根据它的hash值可计算出在Entry数组中的位置,实际上值是存放在它的一个内部类ThreadLocalMap中的Entry
  • 在每次的get和set方法中,都会对key为null的Entry进行清除
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值