• ThreadLocal源代码3


    public class ThreadLocal1<T> {
        //当创建了一个 ThreadLocal 的实例后,它的散列值就已经确定了//threadLocal实例的hashCode是通过nextHashCode()方法实现的,该方法实际上总是用一个AtomicInteger(初始值为0)加上0x61c88647来实现的。
        //0x61c88647这个数是有特殊意义的,它能够保证hash表的每个散列桶能够均匀的分布,这是Fibonacci Hashing,
        //0x61c88647 这个就比较神奇了,它可以使 hashcode 均匀的分布在大小为 2 的 N 次方的数组里。下面写个程序测试一下:
        //也正是能够均匀分布,所以threadLocal选择使用开放地址法来解决hash冲突的问题。
        /*public static void main(String[] args) {
            AtomicInteger hashCode = new AtomicInteger();  // 一直在增加
            int hash_increment = 0x61c88647;
            int size = 16;
            List <Integer> list = new ArrayList <> ();
            for (int i = 0; i < size; i++) {
                list.add(hashCode.getAndAdd(hash_increment) & (size - 1));
            }
            System.out.println("original:" + list);
            Collections.sort(list);
            System.out.println("sort:    " + list);
        }
        size=16:   [7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9, 0]       [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
        size=32  [7, 14, 21, 28, 3, 10, 17, 24, 31, 6, 13, 20, 27, 2, 9, 16, 23, 30, 5, 12, 19, 26, 1, 8, 15, 22, 29, 4, 11, 18, 25, 0]
                 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
        */
        private final int threadLocalHashCode = nextHashCode();//对象的属性,不变化的。
        private final static int HASH_INCREMENT = 0x61c88647;//1640531527。类的属性。
        //类的属性,对象共享,一直在增加。
        private static AtomicInteger nextHashCode = new AtomicInteger();//0
    
        private static int nextHashCode() {
            return nextHashCode.getAndAdd(HASH_INCREMENT);//nextHashCode自己变成了HASH_INCREMENT=1640531527
        }
    
        protected T initialValue() {//用于重写
            return null;
        }
    
        public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
            return new SuppliedThreadLocal<>(supplier);
        }
    
        public ThreadLocal() {
        }
    
        //有可能第一次调用get不调用set,map为null,setInitialValue-initialValue赋予map=threadLocals初值。
        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);//返回调用get()的线程的threadLocals,是一个ThreadLocalMap,这个ThreadLocalMap是ThreadLocal的内部类。
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();//如果map为空,也就是第一次没有调用set直接get(或者调用过set,又调用了remove)时,为其设定初始值
        }
    
        private T setInitialValue() {
            T value = initialValue();//initialValue方法为第一次调用get方法提供一个初始值。
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
            return value;
        }
        /*  ThreadLocal<Integer> x = new ThreadLocal<Integer>();
         for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                public void run() {
                    x.set(new Random().nextInt()); 
                }
            }).start();
        }*/
        //A,B,C线程通过共享变量ThreadLocal<Integer> x.set(s)。A,B,C线程分别修改的是自己线程的threadLocals=ThreadLocalMap
        // 别的线程修改不了这个线程的threadLocals,所以对这个线程的ThreadLocalMap修改时候没有线程安全问题。
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    
        public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
        }
    
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;//返回这个线程的threadLocal属性
        }
    void createMap(Thread t, T firstValue) {//threadLocals是一个ThreadLocalMap,key是调用ThreadLocal方法的这个ThreadLocal。 //ThreadLocal的方法肯定是ThreadLocal对象在调用。 t.threadLocals = new ThreadLocalMap(this, firstValue); }
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }
    T childValue(T parentValue) {
    throw new UnsupportedOperationException(); } static final class SuppliedThreadLocal<T> extends ThreadLocal<T> { private final Supplier<? extends T> supplier; SuppliedThreadLocal(Supplier<? extends T> supplier) { this.supplier = Objects.requireNonNull(supplier); } @Override protected T initialValue() { return supplier.get(); } } static class ThreadLocalMap1 {//ThreadLocalMap里面有一个数组table,table里面的元素是Entry extends WeakReference。 //WeakReference里面属性有value和referent=ThreadLocal。ThreadLocal是被弱引用关联。 //ThreadLocal由强引用变成了弱引用,因为Entry继承了WeakReference,在Entry的构造方法中,调用了super(k)方法就会将threadLocal实例包装成一个WeakReferenece static class Entry extends WeakReference<ThreadLocal<?>> { Object value;//value是真正需要存储的Object//ThreadLocalMap的Entry本身就是虚引用, //WeakReference<B> weakReference = new WeakReference<B>(b1, rq); b1=null之后,weakReference就回去排队。 //Entry继承WeakReference,ThreadLocal=k释放了整个Entry就会去排队 Entry(ThreadLocal<?> k, Object v) {//ThreadLocal放在WeakReference里面。 super(k);//ThreadLocal=null被gc后该 Entry 就会进入到 ReferenceQueue 中 value = v; } } private static final int INITIAL_CAPACITY = 16; private Entry[] table; private int size = 0;//实际个数 private int threshold; private void setThreshold(int len) {//加载因子为2/3,所以哈希表可用大小为:16*2/3=10,即哈希表可用容量为10。 threshold = len * 2 / 3;//threshold是总共容量的2/3。 } private static int nextIndex(int i, int len) {//i+1对len取余 return ((i + 1 < len) ? i + 1 : 0); } private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1);//循环 } //ThreadLocalMap是线程的属性,key是一个个的ThreadLocal,value是值。 ThreadLocalMap1(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);//threshold是总共容量的2/3。 } private ThreadLocalMap1(ThreadLocalMap1 parentMap) { Entry[] parentTable = parentMap.table;//获取table int len = parentTable.length;//设置容量大小 setThreshold(len);//设置threshold table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j];//获取每一个Entry if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();//获取key是ThreadLocal if (key != null) { Object value = key.childValue(e.value);//e.value获取的是value Entry c = new Entry(key, value);//构造新的Entry int h = key.threadLocalHashCode & (len - 1);//计算table的索引 while (table[h] != null) h = nextIndex(h, len);//索引加一重新计算索引 table[h] = c;//放入table size++; } } } } 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// hash冲突 return getEntryAfterMiss(key, i, e); } //ThreadLocalMap 中采用开放定址法,所以当前 key 的散列值和元素在数组中的索引并不一定完全对应。 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) {//e是i位置的值,为null直接退出 ,null后面有key相等也不管了null为边界。所以get(61)就get不到了 ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null)//get时候遇到脏数据擦除 。因为强引用的ThreadLocal在外部置位了空。擦除这个位置时候,还会向右以null为边,擦除其他脏数据。 expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; } //set也会擦除脏数据。设置时候不一定是新增,有可能是之前已经有了去替换 private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; //哈希表大小总是为2的幂次方,所以相与等同于一个取模的过程, int i = key.threadLocalHashCode & (len-1); //如果不考虑remove,所有跟i相关的都在i一起,并且是以null为边界的。考虑remove,所有跟i一样的是在一起并且null为边界,但是中间有可能有null//e=null退出说明没有冲突,不为null说明冲突了。 线性探测。 向右移动,null为边界 for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {//环形查找,不会死循环,因为会扩容。死循环是正常数据占据了全部位置。 ThreadLocal<?> k = e.get(); if (k == key) {//相等就覆盖 e.value = value; return; } //放到脏数据位置,脏数据后面有key相等的也不管了。放脏数据位置时候会向右一直找到null看有没有key相等的null后面有key相等的也不管了。最后放到脏数据位置 if (k == null) { replaceStaleEntry(key, value, i); return; } } //i个位置e=null,直接放 tab[i] = new Entry(key, value); int sz = ++size; //从i位置开始清除,清除掉了就不用hash,没有清除掉就hash。 if (!cleanSomeSlots(i, sz) && sz >= threshold)//size > len * 2 / 3 rehash(); } 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)]) {//null后面有key相等的也不清除了。 if (e.get() == key) {//是根据ThreadLocal=key来判断是否同一个。 e.clear();//置WeakReference里面强引用referent=ThreadLocal=null,WeakReference里面清除引用关联只能clear()。因为是私有的。 expungeStaleEntry(i);//清除i位置,并向右以null为界清除其他脏数据 return; } } } //替换到脏数据staleSlot位置:也不是放到脏数据位置,而是从staleSlot开始向右一直到null先找一遍,有key相等就交换 并放到staleSlot并清除这个位置,否则放到脏数据staleSlot位置//replaceStaleEntry并不仅仅局限于处理当前已知的脏entry,它认为在出现脏entry的相邻位置也有很大概率出现脏entry,所以为了一次处理到位,就需要向前环形搜索,找到前面的脏entry private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length;//总长度,不是实际长度。 Entry e; int slotToExpunge = staleSlot; //slotToExpunge是准备要清理的位置。 //slotToExpunge=null为边界左边最远脏数据位置。e都不为null就是死循环。所以肯定有null的。null在最后几个。remove也会有null。 //循环都是以null作为边界的。null后面有key相等也不要了 for (int i = prevIndex(staleSlot, len);/*往前移动一个位置,会又走回来 */ (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; // 清除数据也是以null作为边界的。 // 向右找,null就退出,中间有key相等的就停止。都不为null也没有相等的就死循环。 for (int i = nextIndex(staleSlot, len);/*往右移动一个位置,会又走回来 */ (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == key) {//向右移动,找到key相等的就去覆盖。 key跟i位置相等 e.value = value; tab[i] = tab[staleSlot];//staleSlot是脏数据位置。i位置是key一样的位置。 tab[staleSlot] = e;//e还是放在了staleSlot位置。i这个key相等的位置直接被脏数据覆盖了。 //slotToExpunge == staleSlot不可能向左找又回来了, 找回来就是全部不是null,就是死循环。 if (slotToExpunge == staleSlot)//向左没有找到脏数据。第一个for循环没有改变slotToExpunge的值。 slotToExpunge = i;//清除的位置为i,否则清除位置是左边的脏数据。肯定要以左边的清除位置为准,因为可以清除的更多 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);//expungeStaleEntry(slotToExpunge)清除slotToExpunge位置, //slotToExpunge到返回值位置都已经没有脏数据,cleanSomeSlots(expungeStaleEntry(slotToExpunge), len)是从返回值位置开始清除脏数据 return; } if (k == null && slotToExpunge == staleSlot)//slotToExpunge == staleSlot说明向左找又回来了, 说明e都不为null,会是死循环。 slotToExpunge = i;//左边没有脏数据右边i是脏数据。第一个for循环没有改变slotToExpunge的值。 清除的位置变为i。否则清除位置是左边的脏数据。肯定要以左边的清除位置为准,因为可以清除的更多 } //像右走,一直到e=null位置,都没有找到key相等的位置。就放到staleSlot脏数据位置 tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // staleSlot是放数据的位置,不能清除。 if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); } //清除i位置,并且i向右一直到null清除或者重新找位置 private int expungeStaleEntry(int staleSlot) { Entry[] tab = table;//局部变量是为了增强引用,不回收。 int len = tab.length; // staleSlot位置 置位null。 tab[staleSlot].value = null;//Entry里面的value解除关系,Entry里面的referent已经解除关系了。 tab[staleSlot] = null;//Entry断掉引用关系, size--; // staleSlot位置移除可,往后一直在null,Rehash Entry e; int i; //上面把staleSlot位置置为null了。从staleSlot向右一个个看,key为null就清除,不为null就重新算位置。e=null退出循环。 for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get();//get()方法返回的是referent=ThreadLocal。 if (k == null) {//e不为null key是null就移除, e.value = null; tab[i] = null;//value移除,entyr解除关系,ThreadLocal已经解除关系为null了。 size--; } else { int h = k.threadLocalHashCode & (len - 1); if (h != i) {//重新找位置h tab[i] = null; while (tab[h] != null) h = nextIndex(h, len);//h有元素,重新找h tab[h] = e;//放到h的位置上去 } } } return i;//旧i和新i之间全部没有脏数据了新i是null的位置。 } //本来只准备i开始移动log2(n)次,移动过程中发现有脏数据,就改变移动次数再次移动log2(len)次 private boolean cleanSomeSlots(int i, int n) {//新插入的位置i和实际大小n boolean removed = false;//并没有真正的清除,只是找到了要清除的位置,而真正的清除在 expungeStaleEntry(int staleSlot) 里面 Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len);//从i开始一个个向后看。循环log2(n)趟i一直加到i+log2(n),可以循环回来。 Entry e = tab[i]; if (e != null && e.get() == null) { n = len;//如果在扫描过程中遇到脏entry的话就会令n为当前hash表的长度(n=len),再扫描log2(n)趟, //注意此时n增加无非就是多增加了循环次数,增加循环次数就是i向右增加到更大的位置而已。 removed = true; i = expungeStaleEntry(i);//清除i位置,并且i向右一直到null清除或者重新找位置。旧i到新i之间全部没有脏数据了下次从新i开始就可以了 } //执行 对数次数 数量的扫描,是一种 基于不扫描(快速但保留垃圾)和 所有元素扫描之间的平衡 } while ( (n >>>= 1) != 0);//n除以2,n用来控制扫描趟数(循环次数),在扫描过程中,如果没有遇到脏entry就整个扫描过程持续log2(n)次,log2(n)的得来是因为n >>>= 1,每次n右移一位相当于n除以2。 // return removed; } private void rehash() {//首先会清理陈旧的 Entry,如果清理完之后元素数量仍然大于 threshold 的 3/4,则进行扩容操作(数组大小变为原来的 2倍) expungeStaleEntries(); if (size >= threshold - threshold / 4) //size >= 3/4*threshold, size >= 1/2*len resize();//扩容 } 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) {//遍历过程中如果遇到脏entry的话直接另value为null,有助于value能够被回收 e.value = null; // 帮助GC } else {//重新hash int h = k.threadLocalHashCode & (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } setThreshold(newLen); size = count; table = newTab; } /** */ 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)//key为null的有问题元素。 expungeStaleEntry(j); } } } }

     

  • 相关阅读:
    table变宽格式
    IE11兼容性设定
    Spring AOP注解失效的坑及JDK动态代理
    关于何时执行shiro AuthorizingRealm 里的 doGetAuthenticationInfo与doGetAuthorizationInfo
    后端接收json数据交互
    关于JavaDate数据返回到前端变数字的问题(并引申到前后端时间的传输)
    git 列出两个分支 或者两个提交版本之间不同的文件名字
    map put相同的key
    MyBatis 中如何调用 Java 的 enum (枚举) 字段
    @ResponseBody 和 @RequestBody 的作用
  • 原文地址:https://www.cnblogs.com/yaowen/p/10913596.html
Copyright © 2020-2023  润新知