Introduction

ThreadLocal defines a static internal class ThreadLocalMap . The key of this Map is the current ThreadLocal , and the value is the thread local variable to be stored. To be precise, the key saves the weak reference ThreadLocal ThreadLocal.ThreadLocalMap will be maintained in each thread. The benefits of doing so are self-evident. It can be isolated between threads, and as the thread is closed, ThreadLocalMap and its object values will be recycled.

Insert data

ThreadLocalMap current thread, and insert it directly if it has been initialized, or initialize it before inserting it if it has not been initialized.

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

We first analyze ThreadLocalMap of set method

private void set(ThreadLocal<?> key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            // 根据Hash值计算数据该插入的位置
            // 这个Hash值得获取是通过一个魔数递增的方式得到
            int i = key.threadLocalHashCode & (len-1);
            // 遍历桶,直到遇到桶中空槽位置停止
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //key相同做覆盖操作
                if (k == key) {
                    e.value = value;
                    return;
                }
                // key为null,说明已经失效,作为弱引用,已被GC
                // 此时需要清理过期Key对应的value
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //走到这里,说明遇到了一个空的槽,则直接插入
            tab[i] = new Entry(key, value);
            int sz = ++size;
            //先清理过期的槽,并判断是否要扩容
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                // 进行扩容,2倍,扩容前也会清理过期的槽
                rehash();
        }

It can be seen that in a simple set method, there are multiple times involved in cleaning up expired slots. Although there are multiple cleaning methods, the expungeStaleEntry method is eventually called. The input parameter is the position of an expired slot in the bucket.

 private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            // 当前过期的槽置为null,方便GC
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            Entry e;
            int i;
            //从当前过期槽的下一个位置开始遍历,直到遇到空槽
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                //这里将过期的槽置为null
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    //注意:走到这个分支,当前的槽未失效,前面过期槽的位置已经被腾出
                    //获取槽i处结点原本应存放的位置
                    //因为ThreadLocal解决Hash冲突的方式,h必定小于等于i
                    int h = k.threadLocalHashCode & (len - 1);
                    //h!=i,说明i处存放的结点不是它原本位置,需要挪动
                    if (h != i) {
                        //将该处的结点挪走,置为null
                        tab[i] = null;
                        // 从它原本应存放的位置h,开始遍历,
                        // 找到一个最靠近h位置的空槽,并插入数据
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            // 返回的桶中空槽的位置
            return i;
        }

Through the above source code analysis, the expungeStaleEntry method does two things:

  • Traverse the bucket, clean up the value and slot corresponding to the expired key (stop when it encounters an empty slot)
  • When encountering a slot that has not failed, try to move the node of the slot closer to its original location
    Regarding the second point, we need to add a ThreadLocal resolve Hash conflicts:
    When ThreadLocalMap value in 060e3106e78c5a, if the current position is already occupied and the key is not the same, then it will find an empty slot to store it, that is, the position of the element in the Map bucket may not be the element itself should be stored The location is backward, so after clearing the expired slot, try to move it as close to the original location as possible. This is for the convenience of query

Get value

In fact, after analyzing the set method, we can roughly guess what the query method is. It is nothing more than counting the location of the bucket based on the key. If the key at that location is not the same as the current key, then traverse back and find the key. For nodes with the same value, the expired slots will be cleaned up by the way during the traversal process.

  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;
            }
        }
        //往map中存一个null的Value,并返回
        return setInitialValue();
    }

Simply look at the get method in map

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

SanPiBrother
24 声望3 粉丝

菜鸡的救赎之路