ThreadLocal源码学习笔记(一)

目录

ThreadLocal的数据结构

ThreadLoacl的弱引用关系图

弱引用

ThreadLocal重要的方法

get

getEntry

set

rehash

remove

threadLocalHashCode:定制的哈希算法

ThreadLocal的作用


ThreadLocal的数据结构

这张图可以看出Thread、ThreadLocal、ThreadLocalMap之间的关系。

ThreadLoacl的弱引用关系图

  • 一个Thread仅有一个ThreadLocalMap对象
  • 一个Entry对象的Key弱引用指向ThreadLocal对象
  • 一个ThreadLocalMap对象存储多个Entry对象
  • 一个ThreadLocal对象可以被多个线程共享
  • ThreadLocal对象不持有Value,Value由Entry持有

弱引用

如果弱引用指向的对象只存在弱引用这一条线路,则在下一次 YGC 时会被回收。 由于 YGC 时间的不确定性,弱引用何时被回收也具有不确定性。弱引用主要用于指向某个易消失的对象,在强引用断开后,此引用不会劫持对象。调用 WeakReference.get()可能返回null ,要注意空指针异常。

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

ThreadLocal重要的方法

get

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//从当前线程中获取ThreadLocalMap
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//查询当前ThreadLocal变量实例对应的Entry
            if (e != null) {//如果不为null,获取value,返回
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }//如果map为null,即还没有初始化,走初始化方法
        return setInitialValue();
    }

    private T setInitialValue() {
        T value = initialValue();//该方法默认返回null,用户可自定义
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)//如果map不为null,把初始化value设置进去
            map.set(this, value);
        else//如果map为null,则new一个map,并把初始化value设置进去
            createMap(t, value);
        return value;
    }

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

    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];//初始化容量16
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);//设置阈值
    }
    //阈值设置为容量的*2/3,即负载因子为2/3,超过就进行再哈希
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
     }

总结get步骤:

1)从当前线程中获取ThreadLocalMap,查询当前ThreadLocal变量实例对应的Entry,如果不为null,获取value,返回

2)如果map为null或entry为null,即还没有初始化,走初始化方法

getEntry

 getEntry时如果第一个元素不是要获取的元素,那么就采用线性探测法遍历当前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;
        }

set

private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);// 根据哈希码和数组长度求元素放置的位置,即数组下标
            //从i开始往后一直遍历到数组最后一个Entry
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //如果key相等,覆盖value
                if (k == key) {
                    e.value = value;
                    return;
                }
                //如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
               //如果超过阀值,就需要再哈希了
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
}

总结set步骤:

1)根据哈希码和数组长度求元素放置的位置,即数组下标

2)从第一步得出的下标开始往后遍历,如果key相等,覆盖value,如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据

3)如果超过阀值,就需要再哈希:

  • 清理一遍陈旧数据 
  • >= 3/4阀值,就执行扩容,把table扩容2倍==》注意这里3/4阀值就执行扩容,避免迟滞
  • 把老数据重新哈希散列进新table

rehash

private void rehash() {
            expungeStaleEntries();// 清理一次陈旧数据,这里涉及到ThreadLocal的内存泄漏的解决,在下一篇文章中会写

            // 清理完陈旧数据,如果>= 3/4阀值,就执行扩容,避免迟滞
            if (size >= threshold - threshold / 4)
                resize();
        }

        /**
         * 把table扩容2倍,并把老数据重新哈希散列进新table
         */
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;
            // 遍历Entry[]数组
            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {// 如果key=null
                        e.value = null; // 把value也置null,有助于GC回收对象
                    } else {// 如果key!=null
                        int h = k.threadLocalHashCode & (newLen - 1);// 计算hash值 
                        while (newTab[h] != null)// 如果这个位置已使用
                            h = nextIndex(h, newLen);// 线性往后查询,直到找到一个没有使用的位置,h递增
                        newTab[h] = e;//在第一个空节点上塞入Entry e
                        count++;// 计数++
                    }
                }
            }

            setThreshold(newLen);// 设置新的阈值(实际set方法用了2/3的newLen作为阈值)
            size = count;// 设置ThreadLocalMap的元素个数
            table = newTab;// 把新table赋值给ThreadLocalMap的Entry[] table
        }

    

remove

Entry继承了WeakReferance,所以它的key是弱引用类型,当外部的ThreadLocal被置为null后,key失去了

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);//调用ThreadLocalMap删除变量
}

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();//调用Entry的clear方法
            expungeStaleEntry(i);//清除陈旧数据
            return;
        }
    }
}
//看一下clear方法如下:把弱引用的对象置null。
public void clear() {
    this.referent = null;
}

threadLocalHashCode:定制的哈希算法

只要是定位元素的地方,都需要调用key.threadLocalHashCode & (len-1)来获得元素所在的entry数组在table中的下标,这个hashcode是ThreadLocalMap定制的哈希算法计算获得的:

元素散列算法如下:

  1. 求hashCode = i*HASH_INCREMENT+HASH_INCREMENT每次新增一个元素(threadLocal)进Entry[],自增0x61c88647
  2. 元素散列位置(数组下标)= hashCode & (length-1),

HASH_INCREMENT = 0x61c88647

使用这个方法来检测该算法的优略

 private static void hashCode(Integer length){
        int hashCode = 0; 
        for(int i=0;i<length;i++){
            hashCode = i*HASH_INCREMENT+HASH_INCREMENT;//每次递增HASH_INCREMENT
            System.out.print(hashCode & (length-1));//求散列下标,算法公式
            System.out.print(" ");
        }
        System.out.println();
 }
    public static void main(String[] args) {
        hashCode(16);//初始化16
        hashCode(32);//后续2倍扩容
        hashCode(64);
    }
7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0 --》Entry[]初始化容量为16时,元素完美散列  
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--》Entry[]容量扩容2倍=32时,元素完美散列
7 14 21 28 35 42 49 56 63 6 13 20 27 34 41 48 55 62 5 12 19 26 33 40 47 54 61 4 11 18 25 32 39 46 53 60 3 10 17 24 31 38 45 52 59 2 9 16 23 30 37 44 51 58 1 8 15 22 29 36 43 50 57 0 --》Entry[]容量扩容2倍=64时,元素完美散列

根据运行结果,代表此算法在长度为2的N次方的数组上,确实可以完美散列。

ThreadLocal的作用

复杂的线程方法可能需要调用很多方法来实现某个功能,这时候ThreadLocal用于一个线程内,跨类、跨方法传递数据。 如果没有ThreadLocal,那么相互之间的信息传递,势必要靠返回值和参数,这样会造成类之间的耦合。

  • hreadLocal不是用来解决线程安全问题的,多线程不共享,不存在竞争,目的是线程本地变量且只能单个线程内维护使用。
  • ThreadLocal可以做业务间的参数透传,但是这些业务必须发生在一个线程当中。
  • 项目如果使用了线程池,那么小心线程回收后ThreadLocal变量要remove,否则线程池回收后,变量还在内存中,后果不堪设想!(例如Tomcat容器的线程池,可以在拦截器中处理:extends HandlerInterceptorAdapter,然后复写afterCompletion方法,remove掉变量)

ThreadLocal源码开头的注释里写道:ThreadLocal被static修饰时的作用

This class provides thread-local variables.  These variables differ from
their normal counterparts in that each thread that accesses one (via its
{@code get} or {@code set} method) has its own, independently initialized
copy of the variable.  {@code ThreadLocal} instances are typically private
static fields in classes that wish to associate state with a thread (e.g.,
a user ID or Transaction ID).
 /**
  * For example, the class below generates unique identifiers local to each thread.
  *	A thread's id is assigned the first time it invokes {@code ThreadId.get()}
  *	and remains unchanged on subsequent calls.
  */
 import java.util.concurrent.atomic.AtomicInteger;

 public class ThreadId {
     // Atomic integer containing the next thread ID to be assigned
     private static final AtomicInteger nextId = new AtomicInteger(0);

     // Thread local variable containing each thread's ID
     private static final ThreadLocal&lt;Integer&gt; threadId =
         new ThreadLocal&lt;Integer&gt;() {
             &#64;Override protected Integer initialValue() {
                 return nextId.getAndIncrement();
         }
     };

     // Returns the current thread's unique ID, assigning it if necessary
     public static int get() {
         return threadId.get();
     }
 }

参考资料:

ThreadLocal终极源码剖析-一篇足矣!https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_label1

《码出高效》第七章节:ThreadLocal的价值

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值