ThreadLocal,可以理解为线程的局部变量,作用就是为每一个使用该变量的线程都提供一个变量值的副本,每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?
每个线程中都有一个ThreadLocalMap(Thread.threadLocals),用于存储每一个线程的变量的副本。
ThreadLocalMap使用数组Entry[] table保存ThreadLocal-->Object键值对象,数组保存位置:int i = key.nextHashCode() & (table.length - 1);
ThreadLocal和Synchonized区别:
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);
}
ThreadLocalMap 中 Entry[] table 的大小必须是2的N次方(INITIAL_CAPACITY = 2^N),那INITIAL_CAPACITY - 1的二进制表示就是低位连续的N个1, 那 firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);的值就是 threadLocalHashCode 的低N位。
测试:
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
public static void main(String[] args) {
for (int j = 0; j < 5; j++) {
int size = 2 << j;
// hash = 0;
int[] indexArray = new int[size];
for (int i = 0; i < size; i++) {
indexArray[i] = nextHashCode() & (size - 1);
}
System.out.println("indexs = "+ Arrays.toString(indexArray));
}
}
结果:
indexs = [0, 1]
indexs = [2, 1, 0, 3]
indexs = [2, 1, 0, 7, 6, 5, 4, 3]
indexs = [2, 9, 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11]
indexs = [18, 25, 0, 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]
没有看到重复的索引值,要哈希表的大小是2的N次方,那么基本上可以保证每次计算出的index值都不会重复。
为什么HashCode不直接用自增的方式(HASH_INCREMENT=1)?
我的理解是,随着不用的 ThreadLocal 变量被回收掉,这种自增的方式的性能会越来越差,因为临近的 slot 为空的可能性很小。而 ThreadLocal 实际所采用的方式,其下标是在跳跃分布,这样即使出现冲突,在临近找到空 slot 的可能性更大一些,性能也会更好。
维护每个线程的ThreadId:
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<Integer> threadId = new ThreadLocal<Integer>(){
@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();
}
}