Android 开发也要掌握的Java知识 -ThreadLocal

Posted 进击的包籽

tags:

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

文章目录

1.ThreadLocal了解

1.1 基本使用

  • 不同线程,使用同一个ThreadLocal,存放数据,互不干扰。
 	    ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
   		hreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
   		
    	private void threadLocalTest() 
        //主线程
        stringThreadLocal.set("baozi111");
        integerThreadLocal.set(111);
        Log.d(TAG, Thread.currentThread().getName() + ":" + stringThreadLocal.get());
        Log.d(TAG, Thread.currentThread().getName() + ":" + integerThreadLocal.get());

        //子线程
        new Thread(new Runnable() 
            @Override
            public void run() 
                stringThreadLocal.set("baozi222");
                integerThreadLocal.set(222);
                Log.d(TAG, Thread.currentThread().getName() + ":" + stringThreadLocal.get());
                Log.d(TAG, Thread.currentThread().getName() + ":" + integerThreadLocal.get());
            
        ).start();
    

1.2 基础知识

  • android中Handler消息机制非常重要,到处都离不开Handler,每个线程只能有一个Loop,就是利用ThreadLocal
  • ThreadLocal和Synchonized有什么区别呢,Synchonized是利用锁的机制,使得变量或者代码块只能一个线程访问,数据共享,而ThreadLocal就是给每个线程一个变量的副本,每个线程都有,且互相不影响,隔离了多个线程的数据共享。
  • ThreadLocal类接口很简单,经常给我调用的只有3个方法,分别是set()、get()、remove()

1.3 原理

  • ThreadLocal本身自己不存储内容,内容由各个线程自己保存,Thread类的 threadLocals,也就是 ThreadLocalMap保存,默认为空,只有在使用时才会初始化,ThreadLocalMap是一个Entry类型数组Entry的key为ThreadLocal,value为保存的内容。每个线程自己保持自己的数据,所以线程之间隔离开了。
  • 上图这个Entry数组里面,不同ThreadLocal,计算的位置不一样,画图只是方便就顺序画了。

2.ThreadLocal源码

2.1 构造方法

  • 构造方法啥也没有,那大概率就是在set的时候初始化各种东西了。
    /**
     * Creates a thread local variable.
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() 
    

2.2 ThreadLocalMap类

2.2.1 Entry

  • 源码可以看到Entry这个类是弱引用的类型。
  • key放ThreadLocal,value放存放的内容。
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> 
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) 
                super(k);
                value = v;
            
        

2.2.2 构造方法

  • 创建一个长度为16的Entry数组,然后存放的位置用hashcode和15做与运算得到,再保存内容。
    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap 

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        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);
        
        .
        .
 

2.2.3 set(ThreadLocal<?> key, Object value)

  • 如果计算的位置已经有内容了,就覆盖,否则就新建一个Entry存放,再判断是否达到阈值,不够就扩容。
       /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) 

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            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();
                
                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();
        

2.2.4 setInitialValue()

  • 初始化空Entry,因为有的线程ThreadLocal一开始没有存东西,但调用了get方法,这时候没内容,但还是先占个坑,返回null。
    /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the @link #get
     * method, unless the thread previously invoked the @link #set
     * method, in which case the @code initialValue method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of @link #remove followed by @link #get.
     *
     * <p>This implementation simply returns @code null; if the
     * programmer desires thread-local variables to have an initial
     * value other than @code null, @code ThreadLocal must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    protected T initialValue() 
        return null;
    

    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    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;
    

2.2.5 getEntry(ThreadLocal<?> key)

  • 利用hashcode找到对应的Entry,如果一次命中找到最好,但扩容过可能不可以一次命中,就要执行 getEntryAfterMiss 方法找。
        /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        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);
        
        
        /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        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;
        

2.2.6 整理扩容

  • 阈值threshold,为数组容量的2/3
  • 当满足条件if (!cleanSomeSlots(i, sz) && sz >= threshold),就是没有删除过,且容量使用了2/3时,先执行**rehash()**就会整理或者扩容。
  • 整理完成,如果满足 if (size >= threshold - threshold / 4) 就会触发扩容,扩容2倍。
        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) 
            //数组长度的2/3
            threshold = len * 2 / 3;
        
        
        /**
         * Re-pack and/or re-size the table. First scan the entire
         * table removing stale entries. If this doesn't sufficiently
         * shrink the size of the table, double the table size.
         */
        private void rehash() 
            //整理
            expungeStaleEntries();

            // Use lower threshold for doubling to avoid hysteresis
            if (size >= threshold - threshold / 4)
                resize();
        

        /**
         * Double the capacity of the table.
         */
        private void resize() 
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            //扩容为原来的2倍
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) 
                Entry e = oldTab[j];
                if (e != null) 
                    ThreadLocal<?> k = e.get();
                    if (k == null) 
                        e.value = null; // Help the GC
                     else 
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    
                
            

            setThreshold(newLen);
            size = count;
            table = newTab;
        

2.3 set(T value)

  • 这个方法就是给当前线程的副本赋设置值,我们在使用时直接在线程里直接使用,在这里会自动切换到当前线程Thread.currentThread()
  • 这里有一个**ThreadLocalMap **,我们可以看到将当前线程存进去了。
  • 如果map存在就传入this也就是ThreadLocal,还有需要保存的value,否则创建,传入当前线程和value
    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the @link #initialValue
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) 
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    

2.3.1 set(this, value)

  • 直接调用 ThreadLocalMap.set 方法,存储或者替换内容。

2.3.2 createMap(Thread t, T firstValue)

  • 创建Map就是调用 ThreadLocalMap构造方法
    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) 
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    

2.4 get()

  • 先获取当前线程的ThreadLocalMap ,然后在ThreadLocalMap 里找对应的Entry,如果找不到就创建一个Entry,占个坑。
    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the @link #initialValue method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() 
    	//获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLocalMap 
        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();
    

3. 最后

  • 理解的如果有错误,希望大家帮忙指出,谢谢~~

以上是关于Android 开发也要掌握的Java知识 -ThreadLocal的主要内容,如果未能解决你的问题,请参考以下文章

Android开发必背 《JAVA 基础》知识点整理

Java开发人员必须掌握的两个Linux魔法工具

新手刚刚开始学习android,求推荐android开发入门教程

面试涨薪攻略指南:Android面试必须掌握的那些Java知识点

面试涨薪攻略指南:Android面试必须掌握的那些Java知识点

面试涨薪攻略指南:Android面试必须掌握的那些Java知识点