JAVA - ThreadLocal 类

ThreadLocal

特性

在当前线程中使用ThreadLocal 存储一个对象时,除非在该线程中重新赋值,否则在该线程中取出来的值,不会因为其他地方调用该变量而改变。
ThreadLocalMap 会在内部Entry.value=传入的对象,保存的是一个副本,而不是变量本身。

代码举例

package threadlocal;

public class ThreadLocalTest {

    private static ThreadLocal<Integer> mThreadLocal = 
                                   new ThreadLocal<Integer>();
    private static ThreadLocal<Boolean> mThreadLocal2 = 
                                   new ThreadLocal<Boolean>();

    private static int value = 1;

    public static void main(String[] args) {
        mThreadLocal.set(value);
        mThreadLocal2.set(true);
        System.out.println(Thread.currentThread().getName() + 
                               "  value = " + mThreadLocal.get());
        System.out.println(Thread.currentThread().getName() + 
                               "  value2 = " + mThreadLocal2.get());

        Thread mThread = new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println(Thread.currentThread().getName() + 
                                  "  value = " + mThreadLocal.get());
                value++;
                mThreadLocal.set(value);
                mThreadLocal2.set(false);
                System.out.println(Thread.currentThread().getName() + 
                                  "  value = " + mThreadLocal.get());
                System.out.println(Thread.currentThread().getName() + 
                                "  value2 = " + mThreadLocal2.get());

            }
        });
        mThread.start();
        try {
            mThread.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + 
                           "  value = " + mThreadLocal.get());
        System.out.println(Thread.currentThread().getName() + 
                           "  value2 = " + mThreadLocal2.get());

    }

}

打印:
main value = 1
main value2 = true
Thread-0 value = null
Thread-0 value = 2
Thread-0 value2 = false
main value = 1
main value2 = true

此处案例用了join(),确保里面的子线程先执行结束。
在进入子线程之前,main线程通过两个ThreadLocal对象存入了1 和 true,
在子线程中,通过两个ThreadLocal 存入 2 和 false,同时取出来,
子线程结束后,main 取的值为先前存入的值,并没有因为子线程使用后值发生改变。

应用案例

Android 中常常使用的Looper
每个线程中创建Looper 时, 可通过Looper.prepare()来创建,并用Looper.loop() 开始循环处理消息。由于每个线程创建Looper 的方式都一样,根据设计思想,一个线程中只需要一个looper即可,那么
如何保证一个线程只有一个looper,如何保证各个looper之间不互相影响?

源码中,在Looper.prepare() 时:

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

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

将创建的Looper 保存到ThreadLocal 中,在里面进行创建一个looper副本, 以这样的形式 key:ThreadLocal value: looper 保存。
当Looper.loop()时,会调用myLooper方法,通过ThreadLocal 可将之前存的looper 取出来使用。这样就可以确保looper 还是之前创建的looper。

ThreadLocal 源码分析

ThreadLocal 主要涉及一下几个类:
Thread: 一个线程,提供ThreadLocal.ThreadLocalMap 属性保存数据
ThreadLocal: 提供set get remove 等方法,开发者使用
ThreadLocalMap: ThreadLocal 内部类,真正实现ThreadLocal 的一些方法
ThreadLocalMap.Entry:ThreadLocalMap的内部类,数据的实际存储类
原理图:
这里写图片描述

ThreadLocal

1. set

    public void set(T value) {
        //获得当前所在线程
        Thread t = Thread.currentThread();
        //获得当前所在线程的ThreadLocalMap属性
        ThreadLocalMap map = getMap(t);     
        if (map != null)
            //如果ThreadLocalMap不为空,那么直接存储数据(ThreadLocal, 值)
            map.set(this, value);           
        else
            //否则创建一个ThreadLocalMap, 直接看createMap(当前线程, 值)
            createMap(t, value);            
    }

ThreadLocal set() 可以分为以下3步:
1. 首先获得当前线程实例,并通过getMap方法,获得其ThreadLocalMap属性。
2. 当前线程有ThreadLocalMap,说明之前已经存过,那么直接用当前的ThreadLocal 作为key,value 作为value,保存到map中。
3. 当前线程没有ThreadLocalMap,则当前线程新建ThreadLocalMap,并将值作为第一组数据存起来。
注意:从这边可以看出,一个线程对应一个ThreadLocalMap;
ThreadLocalMap 存入的是键值对,key: 当前ThreadLocal 对象,value: 传入的对象。

2. get

    public T get() {
        Thread t = Thread.currentThread();//获取当前线程实例
        ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue(); //当前线程没有ThreadLocalMap,则初始化一个
    }

    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);//给当前线程新建ThreadLocalMap
        return value;
    }

    protected T initialValue() {
        return null;
    }

从ThreadLocal get 方法来看,主要分为以下2步:
1. 当前线程有ThreadLocalMap,那么直接用当前的ThreadLocal 作为key,取出value.
2. 当前线程没有ThreadLocalMap,则和set中一样给当前线程新建ThreadLocalMap,并存入初始值,其中initialValue 返回的初始值为null。

3. getMap

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

仅仅返回当前线程的ThreadLocalMap对象。

4. createMap

  // 将当前的Thread 中threadLocals 属性赋值为ThreadLocalMap对象
  void createMap(Thread t, T firstValue) { 
      t.threadLocals = new ThreadLocalMap(this, firstValue);
  }
  static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
      return new ThreadLocalMap(parentMap);
  }

新建ThreadLocalMap 的两种方式,构造方法参数不同:
1. 当前ThreadLocal 上下文, value值
2. 直接传入一个ThreadLocalMap, 此方法在Thread 的初始化中使用

5. remove

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

获取当前Thread 的ThreadLocalMap 属性,直接删除当前ThreadLocal为key 的值。

ThreadLocalMap 的实现

        //存储数据的类型,为弱引用
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         * 默认table 数组的长度
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         * 数组,用来存储数据,因为一个线程中可能不止一个ThreadLocal
         */
        private Entry[] table;

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

        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0

        /**
         * 设置临界值
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * Increment i modulo len.
         * 往后移动
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         * 往前移动
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

解释可见代码
注意注意注意,通过key.threadLocalHashCode & (len - 1) 计算出来的值,该值对应的索引,有概率其他ThreadLocal也会计算得出,此时需要往后移动一位进行保存。下面的查找,删除等都会用到此概念。

1. ThreadLocalMap

        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            //创建数组,来存放Entry;
            table = new Entry[INITIAL_CAPACITY];
             //通过ThreadLocal 的hashcode 以及 长度来定义i值
            int i = firstKey.threadLocalHashCode
                                  & (INITIAL_CAPACITY - 1);
             //存到Entry数组的第i个位置
            table[i] = new Entry(firstKey, firstValue); 
            size = 1; //size = 1;
            //修改临界值  此处设置的为 *2/3,当map中存的值大于这个数时,
            //会进行扩容操作,并修改临界值
            setThreshold(INITIAL_CAPACITY);   
        }

        //传入ThreadLocalMap对象进行初始化
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    ThreadLocal key = e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

ThreadLocalMap的两种构造方法,
1. 看代码解释
2. 直接将传入的parentMap,保存到table 中

2. 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);  //计算i值

            //当i位置 已经存有值了
            //1, key 为当前的ThreadLocal,直接赋值
            //2, key 的值为空,那么说明该位置没释放掉,那么释放key,
            //重新赋值key和value
            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); //i位置为空时,存值
            int sz = ++size; //size 增 1
            //判断是否超出临界点,如果是,则扩容。给数组扩容,i也跟着改变
            if (!cleanSomeSlots(i, sz) && sz >= threshold)  
                rehash();
        }

见代码注释,主要注意2点:
1. 索引的查找,当通过hashcode计算出来的位置有数据值,此时还需要对比key的值是否相同,否则就要继续向后查找空的位置插入数据。
2. 临界点判断,如果超出临界点,那么需要给数组扩容

3. getEntry

        //1, 当前i 中的entry 对应的key 正好是当前的threadlocal
        //2, e为空,直接返回null; 与1相反,i位置被其他占了,则往后寻找
        private Entry getEntry(ThreadLocal key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
             //当前key 正好是要使用的ThreadLocal
            if (e != null && e.get() == key)  
                return e;
            else
                //当前key不是 正在使用的ThreadLocal,则需要往后查找
                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)
                    //清理下没用的key
                    expungeStaleEntry(i);
                else
                   //往后查找 i,看i位置的entry 是否满则条件,
                   //直到k找到,或者e为空时跳出循环
                    i = nextIndex(i, len); 
                e = tab[i];
            }
            return null;
        }

见代码注释,需要注意的是:当key.threadLocalHashCode & (table.length - 1) 计算的索引位置上已经有值,并且key 和当前ThreadLocal 不一致时,那么需要往后查找
1. 找到key 相同,返回value
2. 找到key = null, 清理当前没用的key,可释放空间
3. 直到最后也没找到,返回null

4. remove

        private void remove(ThreadLocal key) {
            Entry[] tab = table;
            int len = tab.length;
            //计算key 应当存着的i值
            int i = key.threadLocalHashCode & (len-1); 
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
              //如果i位置上有值,并且key就是需要删除的,则清除数据
              //否则更改i的值,往后查找,当找到key后,删除数据
                if (e.get() == key) { 
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

见代码注释,注意点和get 方法相同,索引的位置

5. other

       //替换i或i后面位置上,entry不为空但key为空的值
       private void replaceStaleEntry(ThreadLocal key, 
                                   Object value,int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            int slotToExpunge = staleSlot;
            //往前找到key 为null的值
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            //往后继续查找,如果能找到key 那么直接赋值
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal k = e.get();
                if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry
                                         (slotToExpunge), len);
                    return;
                }

                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            //当找不到key时,将当前位置给赋值了
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            //去除多余占用空间的entry
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

        //清理staleslot位置上的值,并将后面的值重新梳理一遍
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot  将当前位置给清空,数量-1
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null 从staleshlot位置往后查找
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal k = e.get();
                 //当发现有key为null时, 将该位置信息删除
                if (k == null) {  
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    //如果key 不为空,看key生成的h值  和 当前的i值是否一致
                    int h = k.threadLocalHashCode & (len - 1); 
                    //查看生成的h位置上是否有值,如果有,那么往后加1,
                    //重新给h定值,并将当前entry 赋值到h位置上
                    if (h != i) {
                        tab[i] = null;
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }
        //清除i位置到size 之间的entry不为空 但 key为空的数据
        private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }
        //梳理一遍,清除没用的key 以及  扩容
        private void rehash() {
            expungeStaleEntries();

            // Use lower threshold for doubling to avoid hysteresis
            if (size >= threshold - threshold / 4)
                resize();
        }
        //给存储数据的Enpty 数组扩容,涉及到数组的重新赋值
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            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) { //key 为空时,清理其对应的值也为空
                        e.value = null; // Help the GC
                    } else {   
                  //如果在对应的h位置上已经有值了,那么往后看有没有空的地方
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }
        //清理所有的位置上key 为空的值
        private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);
            }
        }

见代码注释,
replaceStaleEntry // 替换i或i后面位置上,entry不为空但key为空的值
expungeStaleEntry //清理staleslot位置上的值,并将后面的值重新梳理一遍
cleanSomeSlots //清除i位置到size 之间的entry不为空 但 key为空的数据
rehash //梳理一遍,清除没用的key 以及 扩容
resize //给存储数据的Enpty 数组扩容,涉及到数组的重新赋值
expungeStaleEntries //清理所有的位置上key 为空的值

~看到新问题会继续补充

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值