如何计算ThreadLocal对象的hash值?
- 一、前置知识
- 二、问题
- 三、剖析源码:如何计算ThreadLocal对象的hash值?
- 1、源码
- 1.1 咱先得知道nextHashCode的起始值
- 1.1.1 那就要先了解AtomicInteger
- 创建AtomicInteger
- 原子的增减操作
- 原子的加法操作
- 原子的获取并加法操作
- 原子的比较并设置
- 原子的设置操作
- 1.1.2 继续研究nextHashCode的起始值
- 2、结论
一、前置知识
- 先要了解ThreadLocal的基本知识:
(1)Thread持有的ThreadLocalMap的存储结构,本质是Entry[]数组。
(2)还了解了ThreadLocal提供的3种重要且常用的API:set(…)、get()、remove()。 - 可以看:持续积累ThreadLocal技术【ThreadLocal原理 + ThreadLocal的坑 + ThreadLocal的最佳实践】
二、问题
- 在使用set(…)、get()、remove()时,我们都需要在Entry[]数组中确定一个下标。而这个下标的计算方式都是:int i = key.threadLocalHashCode & (table.length-1);
- 其中,我们都需要计算ThreadLocal对象的hash值(key.threadLocalHashCode),那么,这个hash值怎么算的?
三、剖析源码:如何计算ThreadLocal对象的hash值?
1、源码
// 首次创建ThreadLocalMap对象的时候
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
...
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
...
}
// set(…)、get()、remove()
int i = key.threadLocalHashCode & (table.length-1);
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
...
}
1.1 咱先得知道nextHashCode的起始值
private static AtomicInteger nextHashCode = new AtomicInteger();
1.1.1 那就要先了解AtomicInteger
AtomicInteger是Atomic原子类的一种,是Java多线程板块的重要技术。
咱在这里先不深究,只了解AtomicInteger的基本用法,等在深挖“Atomic原子类”技术时再深究。
- AtomicInteger是java.util.concurrent.atomic包下的一个类,它提供了原子的增减操作,适用于高并发的情况下对整型的操作。
说明:以下代码中的断言(assertEquals)都是成功的。
创建AtomicInteger
public class AtomicIntegerTest {
/**
* 创建AtomicInteger:使用无参构造函数创建一个初始值为0的AtomicInteger对象
*/
@Test
public void testAtomicIntegerWithDefaultInitialValue() {
AtomicInteger atomicInteger = new AtomicInteger();
assertEquals(0, atomicInteger.get());
}
}
/**
* 创建AtomicInteger:使用带参构造函数创建一个初始值为10的AtomicInteger对象
*/
@Test
public void testAtomicIntegerWithCustomInitialValue() {
AtomicInteger atomicInteger = new AtomicInteger(10);
assertEquals(10, atomicInteger.get());
}
原子的增减操作
/**
* 原子的增减操作
*/
@Test
public void testIncrementAndGet() {
AtomicInteger atomicInteger = new AtomicInteger(10);
assertEquals(10, atomicInteger.get());
assertEquals(11, atomicInteger.incrementAndGet());
assertEquals(10, atomicInteger.decrementAndGet());
assertEquals(10, atomicInteger.get());
}
原子的加法操作
@Test
public void testAddAndGet() {
AtomicInteger atomicInteger = new AtomicInteger(10);
assertEquals(10, atomicInteger.get());
assertEquals(20, atomicInteger.addAndGet(10));
assertEquals(20, atomicInteger.get());
}
原子的获取并加法操作
/**
* 原子的获取并加法操作
*/
@Test
public void testGetAndAdd() {
AtomicInteger atomicInteger = new AtomicInteger(10);
assertEquals(10, atomicInteger.get());
assertEquals(10, atomicInteger.getAndAdd(10));
assertEquals(20, atomicInteger.get());
}
原子的比较并设置
/**
* 原子的比较并设置 <br>
* 如果当前的值等于预期的值(expected value),则更新成功,否则更新失败。
*/
@Test
public void testCompareAndSet() {
AtomicInteger atomicInteger = new AtomicInteger(10);
assertEquals(10, atomicInteger.get());
assertEquals(true, atomicInteger.compareAndSet(10, 20));
assertEquals(20, atomicInteger.get());
assertEquals(false, atomicInteger.compareAndSet(10, 30));
assertEquals(20, atomicInteger.get());
}
原子的设置操作
/**
* 原子的设置操作
*/
@Test
public void testAtomicIntegerSet() {
AtomicInteger atomicInteger = new AtomicInteger(10);
assertEquals(10, atomicInteger.get());
atomicInteger.set(20);
assertEquals(20, atomicInteger.get());
}
1.1.2 继续研究nextHashCode的起始值
- 示例
public class ThreadLocalTest {
@Test
public void testThreadLocal() {
Thread thread1 = new Thread(() -> {
// 创建一个ThreadLocal变量,并在一个新的线程内部使用set方法
// 这将触发createMap方法,因为此时线程的threadLocals属性为null【这个主观判断是错误的】
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("Thread 1");
});
thread1.start();
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 首先,要构造一个场景,即当线程执行到ThreadLocal对象.set(…)时,要走createMap(…)。
- 然而,线程初始化时,threadLocals已经不为null。
并且,nextHashCode的初始值不是0,而是626627285(十六进制为255992d5),说明之前已经调用了3次getAndAdd方法。
第4次调用的时候,threadLocalHashCode变为626627285。
2、结论
- 总之,nextHashCode一开始为0,每次加0x61c88647。
那么,按理来说,第1个ThreadLocal对象,它的threadLocalHashCode的值为0、第2个ThreadLocal对象,它的threadLocalHashCode的值为:0 + 0x61c88647,依次类推:0x61c88647 + 0x61c88647、…
private static final int HASH_INCREMENT = 0x61c88647;
这个数是斐波那契数 也叫 黄金分割数(叫啥没那么重要),重要的是让hash 分布非常均匀
(这样冲突的概率就小了)。