问题描述
我们在使用threadLocal的时,使用ThreadLocal.withInitial去初始化而不是使用new ThradLocal去初始化,这是为什么呢?
问题例子
比如说,假设我们想要在每个线程中维护一个独立的计数器
import java.util.concurrent.atomic.AtomicInteger;public class ThreadLocalExample {import java.util.concurrent.atomic.AtomicInteger;public class ThreadLocalExample {private static final ThreadLocal<AtomicInteger> counterThreadLocal = new ThreadLocal<>();public static void main(String[] args) {// 创建多个线程for (int i = 0; i < 5; i++) {new Thread(() -> {// 获取 ThreadLocal 变量并递增计数器int count = counterThreadLocal.get().getAndIncrement();System.out.println("线程" + Thread.currentThread().getId() + " 计数: " + count);}).start();}}
}
这个程序实际会报错
首先要说明一下,这个代码并不是实现多线程的累加
import java.util.concurrent.atomic.AtomicInteger;public class MultiThreadedIncrement {private static final AtomicInteger counter = new AtomicInteger(0);public static void main(String[] args) {// 创建多个线程进行累加for (int i = 0; i < 5; i++) {new Thread(() -> {// 在多线程环境下使用AtomicInteger进行累加for (int j = 0; j < 10000; j++) {counter.incrementAndGet();}}).start();}// 等待所有线程执行完毕try {Thread.sleep(1000); // 这里仅为示例,实际使用中可能需要更精确的等待方式} catch (InterruptedException e) {e.printStackTrace();}// 输出最终累加结果System.out.println("累加结果: " + counter.get());}
}
多线程的累加是这样的,一定要注意。而本案例是想实现每个线程独立维护一个计数器,互不干涉。
或者我们这样来写大家可以看的更清楚
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadLocalIncrement {private static final ThreadLocal<AtomicInteger> threadLocalCounter = ThreadLocal.withInitial(AtomicInteger::new);public static void main(String[] args) {// 创建多个线程进行递增for (int i = 0; i < 5; i++) {new Thread(() -> {// 获取当前线程的计数器AtomicInteger counter = threadLocalCounter.get();// 在每个线程中递增计数器for (int j = 0; j < 10000; j++) {counter.incrementAndGet();System.out.println("线程" + Thread.currentThread().getId() + " 计数: " + counter.get());}}).start();}}
}
我们知道thread底层是基于treadmap实现的
ThreadLocal 使用了一个名为 ThreadLocalMap 的内部类,它是 Thread 类的一个字段。
每个线程都有自己的 ThreadLocalMap 实例,它是一个自定义的散列表结构。在 ThreadLocalMap 中,ThreadLocal 实例作为键,与之关联的值则存储在相应的槽位中。每个 ThreadLocal 实例在自己所属线程的 ThreadLocalMap 中有一个条目。
当你调用 ThreadLocal 的 get 方法时,实际上是在当前线程的 ThreadLocalMap 中查找与该 ThreadLocal 实例关联的值。而调用 set 方法则是在当前线程的 ThreadLocalMap 中设置对应的关联值。
这种设计的好处在于,每个线程都有自己独立的存储空间,不需要锁定或同步,从而提高了并发性能。
总体来说,虽然底层并不直接使用标准的 Map 实现,但可以将 ThreadLocalMap 看作是一个简化版的散列表
解决
我们可以简单理解为这就是一个map,key就是每个线程的threadLacle实例,value就是要存的值
实际上,每个线程在开始执行时,实例化threadLocal并没有初始化值,而是一开始threalLocal的值就是null,所以在执行get方法的时候实际上取得是一个null值。所以null.getAndIncrement()自然会报错
我们可以这样修改
import java.util.concurrent.atomic.AtomicInteger;public class ThreadLocalExample {private static final ThreadLocal<AtomicInteger> counterThreadLocal = new ThreadLocal<>();public static void main(String[] args) {// 创建多个线程for (int i = 0; i < 5; i++) {new Thread(() -> {// 检查 ThreadLocal 变量是否为 null,如果为 null,则设置初始值if (counterThreadLocal.get() == null) {counterThreadLocal.set(new AtomicInteger());}// 获取 ThreadLocal 变量并递增计数器int count = counterThreadLocal.get().getAndIncrement();System.out.println("线程 " + Thread.currentThread().getId() + " 计数: " + count);}).start();}}
}
或者我们也可以使用ThreadLocal.withInitial来避免这个问题
import java.util.concurrent.atomic.AtomicInteger;public class ThreadLocalExample {private static final ThreadLocal<AtomicInteger> counterThreadLocal = ThreadLocal.withInitial(AtomicInteger::new);public static void main(String[] args) {// 创建多个线程for (int i = 0; i < 5; i++) {new Thread(() -> {// 获取 ThreadLocal 变量并递增计数器int count = counterThreadLocal.get().getAndIncrement();System.out.println("线程 " + Thread.currentThread().getId() + " 计数: " + count);}).start();}}
}
总结
在上述例子中,counterThreadLocal 是一个 ThreadLocal 变量,初始值是一个 AtomicInteger 计数器。每个线程第一次访问 counterThreadLocal 时,都会调用 AtomicInteger::new 获取一个新的 AtomicInteger 实例作为初始值。每个线程都有自己独立的计数器,互不干扰。
如果我们使用 new ThreadLocal<>(); 创建 ThreadLocal 实例,它的初始值将为 null。这样,每个线程第一次访问 ThreadLocal 变量时都会得到 null。这可能需要额外的逻辑来处理初始值,增加了出错的可能性。所以,使用 ThreadLocal.withInitial 提供了一种更清晰和线程安全的方式来初始化 ThreadLocal 变量。