ThreadLocal,InheritableThreadLocal源码解析

ThreadLocal

ThreadLocal源码:

ThreadLocal类中首先定义了如下的属性:

// final类型常量表示当前ThreadLocal实例的哈希值,通过方法nextHashCode()计算得出;
private final int threadLocalHashCode = nextHashCode();
// 静态类型变量nextHashCode,为了原子操作整型类AtomicInteger类对象;
private static AtomicInteger nextHashCode = new AtomicInteger();
// 静态类型常量表示哈希值一次增长多少;
private static final int HASH_INCREMENT = 0x61c88647;

静态方法nextHashCode源码如下:

private static int nextHashCode() {
	return nextHashCode.getAndAdd(HASH_INCREMENT);
}

根据以上代码可以推算出ThreadLocal对象实例HashCode的计算规则:

  • 初始值为0;
  • 每创建一个新的ThreadLocal对象,nextHashCode增加0x61c88647,同时新的对象的哈希值为nextHashCode增加之前的值。

为什么不一次增加1,而是一次增长0x61c88647
0x61c88647是一个魔数,对于长度为2的幂次的数组,生成的哈希值可以达到完美散列。可以写一个程序进行验证:

import java.util.concurrent.atomic.AtomicInteger;
public class ThreadLocalReview {
    private static final int HASH_INCREMENT = 0x61c88647;
    private static final AtomicInteger hash = new AtomicInteger();
    public static void main(String[] args) {
        int n = 16;
        while (n > 0) {
            System.out.println(n);
            boolean[] appeared = new boolean[n];
            hash.set(0);
            for (int i = 0; i < n; i++) {
                int hashRes = hash.addAndGet(HASH_INCREMENT) & (n - 1);
                if (appeared[hashRes]) System.out.println(hashRes);
                appeared[hashRes] = true;
            }
            System.out.println("Appeared res:");
            for (int i = 0; i < n; i++) {
                if (!appeared[i]) System.out.println(i);
            }
            n <<= 1;
        }
    }
}

程序从 n = 16 n=16 n=16开始到 n = 2 31 n=2^{31} n=231检测哈希碰撞,结果并没有发现某一个n存在碰撞。

ThreadLocal对象的其他方法中,设置和获取值都是先获取当前线程的Thread对象,接着从Thread对象中获取到ThreadLocalMap对象,接着对ThreadLocalMap对象进行操作。以set()方法为例:

public void set(T value) {
	// 获取当前线程Thread对象
    Thread t = Thread.currentThread();
    // 从当前线程Thread对象中获取到ThreadLocalMap对象;
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    	// map不为空的话就向map中添加键为当前ThreadLocal对象,值为value
        map.set(this, value);
    } else {
    	// 否则为当前线程创建一个新的ThreadLocalMap对象
        createMap(t, value);
    }
}
// ThreadLocalMap对象直接从当前线程的treadLocals属性中获取;
ThreadLocal.ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
// 为当前线程创建一个新的ThreadLocalMap对象,并在ThreadLocalMap对象中添加初始键值对;
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
}

ThreadLocalMap

ThreadLocalMap对象有一个静态内部类Entry用于存储键值对:

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

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

该类继承自WeakReference类,并用WeakReference存储了ThreadLocal对象。一旦线程Thread对象被回收,ThreadLocal对象就只有弱引用,那么ThreadLocal对象在下一次垃圾回收之时必定会被回收。这时候Entry对象中保存的键的值就会变为null
ThreadLocalMap对象的属性有:

// 静态常量,表示Map的初始容量为16;
private static final int INITIAL_CAPACITY = 16;
// Entry数组
private ThreadLocal.ThreadLocalMap.Entry[] table;
// Map存储的键值对数量
private int size = 0;
// 进行resize时的阈值
private int threshold; // Default to 0

ThreadLocalMap默认的构造方法为:

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
	// 初始化Entry数组为初始容量大小
    table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];
    /// 计算初始键值对在数组中的位置,为ThreadLocal的哈希取最后4位
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);
    size = 1;
    //设置阈值的大小;
    setThreshold(INITIAL_CAPACITY);
}
// 可以看到阈值大小为长度的三分之二取整;
private void setThreshold(int len) {
    threshold = len * 2 / 3;
}

ThreadLocalMapset方法为:

private void set(ThreadLocal<?> key, Object value) {
    ThreadLocal.ThreadLocalMap.Entry[] tab = table;
    int len = tab.length;
    // 计算ThreadLocal对象在Entry数组中的位置;
    int i = key.threadLocalHashCode & (len-1);
	// 从下标i开始遍历tab数组;
    for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
		// 如果当前Entry数组的key和新增加的相等,则直接更新value;
        if (k == key) {
            e.value = value;
            return;
        }
		// 如果key的值为null,说明ThreadLocal对象已经被回收,因此用新插入的键值对代替原有的键值对
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
	// 循环结束后的i为新键值对的插入位置
    tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
// 计算当前下标的下一个下标位置,为对下标进行循环;
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {
    ThreadLocal.ThreadLocalMap.Entry[] tab = table;
    int len = tab.length;
    ThreadLocal.ThreadLocalMap.Entry e;
    int slotToExpunge = staleSlot;
    // 从当前的插入位置开始前向遍历tab数组中不为空的元素,找到最开始key为null的entry;
    for (int i = prevIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = prevIndex(i, len))
        if (e.get() == null)
            slotToExpunge = i;
	// 从插入位置开始后向遍历
    for (int i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
		// 如果当前遍历的Entry中的键值为key
        if (k == key) {
            e.value = value;
			// 交换插入位置和i的Entry
            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;
			// 如果将要擦去的位置和插入位置相等,则将将要擦去的位置设置为i
            if (slotToExpunge == staleSlot)
                slotToExpunge = i;
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }
		// 如果k是null而且将要擦去的位置和插入位置相等,则将将要擦去的位置设置为i
        if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
    }
    tab[staleSlot].value = null;
    tab[staleSlot] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
    if (slotToExpunge != staleSlot)
        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

private int expungeStaleEntry(int staleSlot) {
    ThreadLocal.ThreadLocalMap.Entry[] tab = table;
    int len = tab.length;
    // 清除tab数组当前位置的Entry;
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;
    ThreadLocal.ThreadLocalMap.Entry e;
    int i;
    // 从当前位置的下一个开始遍历直到碰到一个元素值为null为止
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        // 如果当前的key是null,则清除当前位置的entry
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
        	// 否则为当前位置的Entry寻找新的插入位置;
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    // 最终返回第一个值为null的位置
    return i;
}

private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    ThreadLocal.ThreadLocalMap.Entry[] tab = table;
    int len = tab.length;
    do {
        i = nextIndex(i, len);
        ThreadLocal.ThreadLocalMap.Entry e = tab[i];
        if (e != null && e.get() == null) {
        	// 如果发现了
            n = len;
            removed = true;
            i = expungeStaleEntry(i);
        }
    //
    } while ( (n >>>= 1) != 0);
    return removed;
}

set方法创建Entry对象,并使用ThreadLocal对象的threadLocalHashCode计算哈希值,按理来说是不会产生冲突的,但是set方法还是 检测到了冲突,并通过for循环从插入位置找起直到第一个为null的位置,或是keynull的位置作为冲突时新增Entry时的插入位置,原因何在?
set方法中,如果发现了某一key值为null说明其已经被垃圾回收,则会调用replaceStaleEntry方法清理Entry数组中的keynullEntry。可以看到方法replaceStaleEntry对从插入位置开始,先前向遍历寻找keynullEntry,接着进行后向遍历,结果有如下两种情况:

  1. 发现一个key和插入的key相等,更新Entryvalue的值并将其与插入位置中的Entry互换,更新插入位置;之后调用expungeStaleEntry方法从插入位置开始到第一个值为null的元素,删除值为nullEntry,为值不为nullEntry重新寻找插入位置:
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
	tab[i] = null;
	while (tab[h] != null)
	h = nextIndex(h, len);
	tab[h] = e;
}

可以看到只有对哈希值与位置不一致的元素才会寻找新位置。新位置是从数组当前位置开始第一个元素值为null的位置。
之后调用cleanSomeSlots方法。该方法以n二进制长度为一个单位进行启发式搜索,如果发现有key的值为null则继续调用expungeStaleEntry方法并重新开始计数。
2. 发现keynullslotToExpunge变量和插入位置相等时更新slotToExpunge;最后调用expungeStaleEntrycleanSomeSlots方法。

set方法的最后如果发现所有的keynullEntry元素已经被清理完了之后数组中元素的大小仍然大于阈值,则执行rehash方法进行重新哈希。

private void rehash() {
	expungeStaleEntries();
	// Use lower threshold for doubling to avoid hysteresis
	if (size >= threshold - threshold / 4)
	    resize();
}
private void resize() {
    ThreadLocal.ThreadLocalMap.Entry[] oldTab = table;
    int oldLen = oldTab.length;
    int newLen = oldLen * 2;
    ThreadLocal.ThreadLocalMap.Entry[] newTab = new ThreadLocal.ThreadLocalMap.Entry[newLen];
    int count = 0;
    for (ThreadLocal.ThreadLocalMap.Entry e : oldTab) {
        if (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == null) {
                e.value = null; // Help the GC
            } else {
                int h = k.threadLocalHashCode & (newLen - 1);
                while (newTab[h] != null)
                    h = nextIndex(h, newLen);
                newTab[h] = e;
                count++;
            }
        }
    }
    setThreshold(newLen);
    size = count;
    table = newTab;
}

rehash方法首先调用expungeStaleEntries方法清理tab数组中的所有keynullEntry,接着判断size是否超过了阈值的 3 4 \frac{3}{4} 43,超过的话才会进行rehash
getEntry方法理解起来相对简单,源码如下:

private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    ThreadLocal.ThreadLocalMap.Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}
private ThreadLocal.ThreadLocalMap.Entry getEntryAfterMiss(ThreadLocal<?> key, int i, ThreadLocal.ThreadLocalMap.Entry e) {
    ThreadLocal.ThreadLocalMap.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;
}

InheritableThreadLocal:

InheritableThreadLocal为父线程和子线程之间提供了可继承的ThreadLocal变量。Java中父线程和子线程的定义为:如果线程A创建了线程B,则AB的父线程,BA的子线程。可以通过以下的方式使用InheritabeThreadLocal对象:

public class ThreadLocalReview {
	// 声明一个静态InheritableTreadLocal对象
    static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
    public static void main(String[] args) {
    	// 在主线程中设置值
        inheritableThreadLocal.set("Parent thread's value");
        // 在主线程中打印出值
        System.out.println(Thread.currentThread().getName() + ' ' + inheritableThreadLocal.get());
        Runnable runner = () -> {
            Thread cur = Thread.currentThread();
            // 在子线程中直接打印值
            System.out.println(cur.getName() + ' ' + inheritableThreadLocal.get());
        };
        Thread childThread = new Thread(null, runner, "childThread", 1000, true);
        childThread.setDaemon(true);
        childThread.start();
    }
}

程序的输出为:

main Parent thread's value
childThread Parent thread's value

可以看到在子线程中虽然没有设置InheritableThreadLocal的值,但是在直接获取的时候还是获取到了父线程的值。
ThreadLocal类的方法中有一childValue方法,该方法会抛出UnsupportedOperationException异常:

T childValue(T parentValue) {
    throw new UnsupportedOperationException();
}

ThreadLocalMap中的某一构造方法中使用了ThreadLocal中的childValue方法,改构造方法的传入参数为父线程的ThreadLocalMap对象:

private ThreadLocalMap(ThreadLocal.ThreadLocalMap parentMap) {
    ThreadLocal.ThreadLocalMap.Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    this.setThreshold(len);
    this.table = new ThreadLocal.ThreadLocalMap.Entry[len];
    ThreadLocal.ThreadLocalMap.Entry[] tab = parentTable;
    int tabLen = parentTable.length;
    for(int i = 0; i < tabLen; ++i) {
        ThreadLocal.ThreadLocalMap.Entry e = tab[i];
        if (e != null) {
            ThreadLocal<Object> key = (ThreadLocal)e.get();
            if (key != null) {
                Object value = key.childValue(e.value);
                ThreadLocal.ThreadLocalMap.Entry c = new ThreadLocal.ThreadLocalMap.Entry(key, value);
                int h;
                for(h = key.threadLocalHashCode & len - 1; this.table[h] != null; h = nextIndex(h, len)) {
                }
                this.table[h] = c;
                ++this.size;
            }
        }
    }
}

可以看到该ThreadLocalMap的构造方法根据父线程的ThreadLocalMap对象创建了新的ThreadLocalMap对象。而新ThreadLocalMap对象中key的值是通过调用ThreadLocal对象的childValue方法获取的。

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    public InheritableThreadLocal() {
    }
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
        return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

可以看到InheritableThreadLocal类只是简单的覆盖了ThreadLocal类的childValue方法,getMap方法和createMap方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值