ThreadLocal源码解析及解决hash冲突的另一种方式-开放定址法(线性探测再散列)

首先看个示例:

public class ThreadLocalTest {
    public static void main(String[] args) {
        ThreadLocal<String> threadLocal1 = new ThreadLocal<String>();
        ThreadLocal<String> threadLocal2 = new ThreadLocal<String>();
        ThreadLocal<String> threadLocal3 = new ThreadLocal<String>();
        threadLocal1.set("main");

        Thread a = new Thread(()->{
            threadLocal1.set("a1");
            threadLocal2.set("a2");
            threadLocal3.set("a3");
            System.out.println(threadLocal1.get()+","+threadLocal2.get()+","+threadLocal3.get());
        },"A");

        a.start();

        Thread b = new Thread(()->{
            threadLocal1.set("b2");
            threadLocal2.set("b3");
            threadLocal3.set("b4");
            System.out.println(threadLocal1.get()+","+threadLocal2.get()+","+threadLocal3.get());
        },"B");

        b.start();

        System.out.println(threadLocal1.get());
    }
}

set过程
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

 获取当前线程对象,然后获取当前线程对象里面的threadLocals属性

class Thread implements Runnable {
   ---------------------------------
    ThreadLocal.ThreadLocalMap threadLocals = null;
   ---------------------------------

threadLocals刚开始是null,是什么时候注入的呢,其实threadLocal 中set  和 get都会去初始化threadLocals

看下 createMap(t, value);

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

其中的this,是ThreadLocal对象,则示例中的threadLocal1 ,threadLocal2 ,threadLocal3,

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

threadLocals 是ThreadLocal.ThreadLocalMap对象

       static class Entry extends WeakReference<ThreadLocal<?>> {
         
            Object value;

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

        private static final int INITIAL_CAPACITY = 16;

        private Entry[] table;

        private int size = 0;

        private int threshold; // Default to 0

        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

其实ThreadLocalMap底层是一个数组,数组里面存放的是Entry <key,value>的键值对,k是 示例中的threadLocal1 ,threadLocal2 ,threadLocal3,value就是示例中的a1,a2,a3

且这个数组支持动态扩容,负载因子是2/3,差不多0.6666,数组初始大小为16

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

则set就是往当前线程 threadLocals 属性变量中的  数组里面插入值

解决hash冲突    开放定址法----线性探测再散列

存放entry是通过hash表存储的,则必然就可能会产生碰撞,那是怎么解决碰撞的呢?

看set方法里面下面这个逻辑:

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

如果定位的位置上有值,判断key是否跟当前位置的值相等,如果不相等,则把要存放的位置重新计算 i = nextIndex(i, len)  ,则就是在当前位置往后去找tab[i]为null的或者与 table[i]中的key一致的

        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

这也是解决hash冲突的一种方式

get过程

    public T get() {
        Thread t = Thread.currentThread();
        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();
    }

主要看ThreadLocalMap.Entry e = map.getEntry(this);这行

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

根据key的hash值,定位到数组的下标取出,如果定位不到,则根据开放定址法,从当前位置往后去寻找

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

这里获取hash值涉及cas操作,放到下节去讲:

    private static AtomicInteger nextHashCode =
        new AtomicInteger();

    /**
     * The difference between successively generated hash codes - turns
     * implicit sequential thread-local IDs into near-optimally spread
     * multiplicative hash values for power-of-two-sized tables.
     */
    private static final int HASH_INCREMENT = 0x61c88647;

    /**
     * Returns the next hash code.
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值