如何计算ThreadLocal对象的hash值?【ThreadLocal技术】(含AtomicInteger的介绍)

一、前置知识

二、问题

  • 在使用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 分布非常均匀(这样冲突的概率就小了)。

相关推荐

  1. ThreadLocal介绍

    2024-01-07 23:28:02       17 阅读
  2. ThreadLocal主要特点:

    2024-01-07 23:28:02       19 阅读
  3. ThreadLocal

    2024-01-07 23:28:02       38 阅读
  4. ThreadLocal

    2024-01-07 23:28:02       21 阅读
  5. ThreadLocal(4):ThreadLocal核心方法源码

    2024-01-07 23:28:02       29 阅读
  6. ThreadLocal内存如何释放

    2024-01-07 23:28:02       35 阅读
  7. 深度解析 ThreadLocal 多重应用场景

    2024-01-07 23:28:02       33 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-07 23:28:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-07 23:28:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-07 23:28:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-07 23:28:02       20 阅读

热门阅读

  1. Dart教程(快速入门2024完整版)

    2024-01-07 23:28:02       47 阅读
  2. 2024年1月7日学习总结

    2024-01-07 23:28:02       41 阅读
  3. vue组件通信的方式

    2024-01-07 23:28:02       39 阅读
  4. WordPress函数get_post()

    2024-01-07 23:28:02       34 阅读
  5. docker安装rabbitmq

    2024-01-07 23:28:02       34 阅读
  6. Go语言范围Range

    2024-01-07 23:28:02       37 阅读
  7. 【http和https】 简单入门了解

    2024-01-07 23:28:02       43 阅读
  8. 决策树(Decision Trees)

    2024-01-07 23:28:02       45 阅读
  9. 62、python - 全手写搭建 resnet50 神经网络

    2024-01-07 23:28:02       43 阅读