美文网首页Android开发Android 技术开发Android性能
Java之ThreadLocal的使用及源码解析

Java之ThreadLocal的使用及源码解析

作者: 饱醉豚我去年买了个表 | 来源:发表于2019-09-29 10:12 被阅读0次

    目录:


    image.png

    ThreadLocal是什么

    ThreadLocal是一个能创建线程局部变量的类。通过ThreadLocal提供的get和set方法,可以为每一个使用该变量的线程保存一份数据副本,且线程之间是不能相互访问的,从而达到变量在线程间隔离、封闭的效果

    使用例子

    public static void main(String[] args) throws InterruptedException {
    
        final ThreadLocal<String> threadLocal = new ThreadLocal<>();
        threadLocal.set("AAA");
    
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("BBB");
                System.out.println("get in " + Thread.currentThread().getName() + " " + threadLocal.get());
            }
        }).start();
    
        Thread.sleep(1000);
        System.out.println("get in main thread " + threadLocal.get());
    }
    

    执行结果:

    get in Thread-0 BBB
    get in main thread AAA
    

    首先,在主线程中初始化了ThreadLocal,并且操作的变量是String类型,在主线程中设置该变量为"AAA",主线程等待1秒钟,同时启动了一个子线程也调用ThreadLocal设置该变量为"BBB"并输出,1秒之后通过get输出主线程的结果,发现子线程设置的值并没有影响主线程中设置的值,即通过ThreadLocal修饰的变量可以实现在各个线程之间互不干扰,相互隔离的效果。

    源码解析

    初始化

    //1
    final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
    threadLocal.set("AAA");
    
    //2
    final ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return "AAA";
        }
    };
    

    对应的源码:

    protected T initialValue() {
        return null;
    }
    
    public ThreadLocal() {
    }
    

    ThreadLocal的初始化可以有上面1、2两种方式,一种是先初始化然后通过set设置值,一种直接重写initialValue并设置值。既然ThreadLocal可以做到变量的线程封闭,我们有理由猜想是不是ThreadLocal<T>是通过Map<Thread,T>来实现的呢?其中key是当前Thread,value是通过set或者initialValue设置的,看似是这样,但ThreadLocal内部并不是这么实现的,接着往下分析。

    set值

    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //根据当前线程获取ThreadLocalMap,注:ThreadLocalMap内部并不是通过map来存储value,而是通过数组存储的
        ThreadLocalMap map = getMap(t);
        if (map != null)
           //不为空,内部直接通过数组设置Entry元素(Entry中包装了ThreadLocal及value,其中key=ThreadLocal,value=传入值value)
            map.set(this, value);
        else
            //为空,则初始化一个ThreadLocalMap,并将ThreadLocal及value包装成Entry放入数组中。
            createMap(t, value);
    }
    
    ThreadLocalMap getMap(Thread t) {
        //threadLocals是Thread类中的成员变量
        return t.threadLocals;
    }
    
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    

    Thread类:

    public class Thread implements Runnable {
    
         /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
         ThreadLocal.ThreadLocalMap threadLocals = null;
       }  
    

    所以set方法首先根据当前线程获取线程中的threadLocals变量(ThreadLocalMap类型),并将ThreadLocal及value包装成Entry放入数组中,因为threadLocals是Thread中的局部变量(存放在栈空间中),所以只有当前线程能访问,其他线程无法访问。这里有个问题:为什么还需要将ThreadLocal作为key传入到ThreadLocalMap呢?因为一个线程中可以初始化多个ThreadLocal,是一对多的关系,所以需要传入ThreadLocal,如果初始化了多个ThreadLocal,根据不同的ThreadLocal可以获得对应的value。那么ThreadLocalMap内部到底是怎么存储的呢?

    ThreadLocal静态内部类ThreadLocalMap:

    static class ThreadLocalMap {
    
        //内部类Entry,继承了弱引用WeakReference,使用ThreadLocal作为键值
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
    
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    
        //初始容量 必须是2个倍数
        private static final int INITIAL_CAPACITY = 16;
    
        //Entry数组,必要时可以扩容,
        private Entry[] table;
    
        //数组大小
        private int size = 0;
    
        //初始化ThreadLocalMap,并将ThreadLocal、firstValue封装成Entry并放入Entry数组中
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
       
        //根据key(ThreadLocal类型)的hash获取Entry在数组中的位置,有数据的话直接返回该数据
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[I];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
    
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
    
            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[I];
            }
            return null;
        }
    
        //根据key(ThreadLocal类型)设置value值
        private void set(ThreadLocal<?> key, Object value) {
    
            Entry[] tab = table;
            int len = tab.length;
            //获取数组中存取位置
            int i = key.threadLocalHashCode & (len-1);
    
            for (Entry e = tab[I];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //如果key值在Entry中存在,那么直接覆盖之前的值
                if (k == key) {
                    e.value = value;
                    return;
                }
    
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
    
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
    
        //移除key对应的value
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[I];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
    }
    

    get值

    public T get() {
        //获取当前thread
        Thread t = Thread.currentThread();
        //根据当前线程获取ThreadLocalMap,注:ThreadLocalMap内部并不是通过map来存储value,而是通过数组存储的
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //根据this(ThreeadLocal)获取数组中对应的Entry,不为空直接取出value
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                T result = (T)e.value;
                return result;
            }
        }
        //如果线程中的ThreadLocalMap为空,则进行初始化
        return setInitialValue();
    }
    
    private T setInitialValue() {
        //初始化值 默认是null
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    

    ThreadLocal在Handler中的使用

    Handler机制:

    handler.png

    Handler构造函数:

    public Handler(Callback callback, boolean async) {
        ......其他代码......
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    

    Looper.prepare初始化:

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
    public static void prepare() {
        prepare(true);
    }
    
    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));
    }
    

    Android中Handler机制在项目中使用的很频繁,Handler底层通过MessageQueue和Looper来实现消息的线程间通信。其中Handler来发送及接收并处理消息,MessageQueue接收Handler发来的消息,并在Looper循环中根据msg.target(handler)来分发消息。一个线程只对应一个Lopper,一个Looper对应一个MessageQueue,但是一个线程中可以有多个Handler。因为一个线程只能对应一个Looper,且Looper跟线程是一一绑定关系,此时用ThreadLocal再合适不过。

    Looper中使用ThreadLocal关联Looper,使得Looper只能在各自线程使用,并且不管handler从哪个线程传来消息,ThreadLocal保证了最终消息在Looper初始化时所在的线程处理。

    总结

    • ThreadLocal存储变量副本实际是保存在每个线程的threadLocals(ThreadLocal.ThreadLocalMap类型)变量中。
    • ThreadLocal包含的对象(指的是ThreadLocal<T>中的T对象)在不同的线程中有不同的副本(实际上也是不同的实例)
    • ThreadLocalMap中的Entry弱引用于ThreadLocal,同时也会回收key为null的Entry,从而避免了Entry无法释放导致内存泄漏

    画一个简易图:

    threadLocal.png

    参考

    【1】https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/
    【2】https://www.cnblogs.com/dolphin0520/p/3920407.html
    【3】http://www.jasongj.com/java/threadlocal/
    【4】https://juejin.im/post/5ba64dcee51d4543e609656d

    相关文章

      网友评论

        本文标题:Java之ThreadLocal的使用及源码解析

        本文链接:https://www.haomeiwen.com/subject/euakuctx.html