8.Java并发编程—ThreadLocal,TreadLocalMap,InheritableThreadLocal,TransmittableThreadLocal使用指南

ThreadLocal

当在Java的多线程环境中需要确保多个线程对同一变量的安全访问时,可以使用ThreadLocal类型的对象。ThreadLocal提供了一种线程封闭的机制,它能够为每个线程创建一个独立的变量副本,使得每个线程都可以独立地访问和修改自己的变量副本,而不会影响其他线程的副本。

1.ThreadLocal的基本使用

当使用 ThreadLocal 时,我们可以使用以下实例方法:

  1. void set(T value)
    • 将当前线程的变量副本设置为指定的值 value。这个值将与当前线程关联,并且只有当前线程可以访问。
  2. T get()
    • 返回当前线程的变量副本的值。每个线程只能访问和获取自己的变量副本,而不会影响其他线程的副本。
  3. void remove()
    • 从当前线程的 ThreadLocal 实例中删除变量副本。这样可以释放当前线程占用的资源,并避免潜在的内存泄漏问题。
  4. protected T initialValue()
    • 在首次访问 ThreadLocalget()set() 方法时,如果当前线程的变量副本尚未创建,则会调用此方法来初始化变量的初始值。默认实现返回 null。你可以通过继承 ThreadLocal 并重写该方法来提供自定义的初始值。
  5. void setInitialValue(T value)
    • 设置所有线程的初始值。该方法将为所有线程创建一个共享的初始变量副本,而不是为每个线程创建独立的副本。这样,所有线程在访问变量时将共享同一个初始值。

下面我们通过一个简单的案例来了解一下 如何使用ThreadLocal

class ThreadLocalExample {
	private static final Logger logger = LoggerFactory.getLogger(ThreadLocalExample.class);
    // 这里我们在外层提供了一个共有的变量 myThreadLocal 当前类下的所有方法都可以进行访问
	private static ThreadLocal<Integer> myThreadLocal = new ThreadLocal<>();

	public static void main(String[] args) {
        // 下面我们创建两个线程,来对共有的ThreadLocal进行修改,观察结果
		// 线程1设置变量值
		Thread thread1 = new Thread(() -> {
			logger.error("Thread 1 - Before: {}", myThreadLocal.get());
			myThreadLocal.set(10);
			logger.error("Thread 1: {}", myThreadLocal.get());
		});

		// 线程2设置变量值
		Thread thread2 = new Thread(() -> {
			logger.error("Thread 2 - Before: {}", myThreadLocal.get());
			myThreadLocal.set(20);
			logger.error("Thread 2: {}", myThreadLocal.get());
		});

		thread1.start();
		thread2.start();

		// 最终来查看线程1和线程2的值是否会相互影响
		logger.error("Main Thread: {}", myThreadLocal.get());
        
	}
}

image-20240326200125246

从结果中可以看出,每个线程本地变量 都绑定了一个独立的值,每次操作都是在自己的值上进行的

1.2.ThreadLocal.withInitial()

如果尚未 设定值,那么在访问的时候 是返回了一个null值的
如果我们希望,在创建的时就给予ThreadLocal一个默认的值 可以使用 ThreadLocal提供的一个默认方法

ThreadLocal.withInitial()ThreadLocal 类提供的一个静态方法,它允许我们使用提供的初始化函数来设置变量副本的初始值。该方法返回一个新的 ThreadLocal 对象,并在首次访问 ThreadLocalget()set() 方法时,调用提供的初始化函数来获取初始值。

class ThreadLocalExample {
	private static final Logger logger = LoggerFactory.getLogger(ThreadLocalExample.class);
	private static ThreadLocal<Integer> defaultThreadLocal = ThreadLocal.withInitial(() -> 110);


	public static void main(String[] args) {

		logger.error("defaultThreadLocal.get() = {}", defaultThreadLocal.get());

	}
}

image-20240326203015626

2.ThreadLocal的使用场景

ThreadLocal解决线程安全问题是一个比较好的方案,它通过每个线程提供一个独立的本地值,去解决并发访问的冲突问题,在很多情况下,ThreadLocal会比使用线程同步机制synchronized更加简单,方便,可以让程序有更好的并发性能

ThreadLocal的使用可以大致分为以下两种类型:

  1. 线程隔离(Thread Isolation):
    ThreadLocal 可以用于实现线程隔离,即为每个线程提供独立的变量副本,使得每个线程都可以独立地访问和修改自己的副本,而不会影响其他线程。这种线程隔离的特性在以下情况下非常有用:
    • 数据库连接管理:在多线程环境中,每个线程需要独立的数据库连接,可以使用 ThreadLocal 来管理每个线程的连接副本,避免线程之间的混淆和资源竞争。
    • 用户身份信息:在 Web 应用程序中,每个请求的用户身份信息可以存储在ThreadLocal中,以便在整个请求处理过程中方便访问,而不必在每个方法参数中显式传递。
    • 事务管理:在多线程的事务环境中,可以使用ThreadLocal存储和传递事务上下文,确保每个线程都能独立地管理自己的事务状态。
  2. 跨函数传递数据(Data Passing across Functions):
    ThreadLocal 也可以用于在函数调用链中跨函数传递数据,避免了在每个函数参数中显式传递数据的麻烦。这种跨函数传递数据的特性在以下情况下很有用:
    • 计时器和日志跟踪:在函数调用链中,可以使用ThreadLocal存储计时器或日志跟踪的上下文信息,通过在每个函数中更新和传递上下文,可以方便地记录函数的执行时间或跟踪日志。
    • 上下文数据传递:在复杂的业务逻辑中,可能需要在多个函数中传递同一份上下文数据,例如请求参数、配置信息等。使用 ThreadLocal 可以将上下文数据存储在 ThreadLocal 中,在整个函数调用链中共享和访问。

下面我会通过两个不同的案例,来介绍ThreadLocal

2.1.线程隔离

下面我们通过线程池来,模拟一下ThreadLocal中,线程隔离的场景

/**
  * ThreadLocal在线程池中传递
  * @throws InterruptedException
  */
@Test
@DisplayName("测试threadLocal在线程池中传递")
public void test2() throws InterruptedException {

    ExecutorService executorService = Executors.newFixedThreadPool(2);
    // 放入threadLocal的值
    threadLocal.set("看看有没有被修改!");


    executorService.submit(() -> {
        logger.error("获取值为:{}", threadLocal.get());
        threadLocal.set("hello,my-thread-local-1");
        logger.error("当前值为:{}", threadLocal.get());
    });

    executorService.submit(() -> {
        logger.error("获取值为:{}", threadLocal.get());
        threadLocal.set("hello,my-thread-local-2");
        logger.error("当前值为:{}", threadLocal.get());

    });

    Thread.sleep(TimeUnit.SECONDS.toMillis(1));
    executorService.submit(() -> {
        logger.error("最终前值为:{}", threadLocal.get());
    });
    executorService.submit(() -> {
        logger.error("最终前值为:{}", threadLocal.get());
    });


    // 这里两个线程,首先原生的ThreadLocal 每个线程的TreadLocal都是单独占有一份的, 所以 pool-2-thread-1,pool-2-thread-2两个线程是不能获取到 main线程的ThreadLocal
    // 注意 每个线程的 ThreadLocal都是【独立的】,【主线程】中的【子线程】无法获取主线程threadLocal



}

image-20240326204337171

在这个例子中,我通过Executros静态方法创建了只有两个线程的固定线程池,模拟在线程池中传递一个 ThreadLocal 变量。

然后,我们使用 threadLocal.set() 方法将值放入 ThreadLocal 变量中

接下来,向线程池中提交两个任务

在每个任务中,通过 threadLocal.get() 方法获取 ThreadLocal 变量的值,并使用 threadLocal.set() 方法修改变量的值。

需要注意的是,由于线程池中的线程是复用的,所以可能会出现线程在不同任务间切换的情况。因此,每个任务都可以独立地访问和修改自己的 ThreadLocal 变量副本,而不会影响其他任务。

最后,我们可以观察到最终的两个任务中,获取到的 ThreadLocal 变量的值仍然是各自任务设置的最新值。

需要注意的是,由于线程池的线程是有限的,如果线程池中的线程数量小于任务数量,那么部分任务可能会等待可用的线程。在等待期间,线程池中的某个线程可能会被重新分配给其他任务,从而导致 ThreadLocal 变量副本的切换。因此,在使用 ThreadLocal 和线程池结合时,需要注意线程切换可能带来的副作用。

总结:通过在线程池中使用 ThreadLocal,我们可以实现线程隔离,确保每个线程都能独立地访问和修改自己的 ThreadLocal 变量副本,而不会影响其他线程。这在需要在线程池中传递数据,并保持数据隔离的场景中非常有用。

2.2.跨函数传递ThreadLocal

当在企业开发中进行跨函数传递时,ThreadLocal 应用场景非常多,下面是一些常见场景:

  1. 认证和授权信息传递:在一个请求处理过程中,用户的认证和授权信息可能需要在多个函数或方法中共享。使用 ThreadLocal 可以方便地传递这些信息,如用户身份、权限等。
  2. 跨层数据传递:在多层架构中,比如前端控制器、服务层、持久化层等,可能需要在不同层之间传递一些公共的数据,如请求信息、上下文数据等。
  3. 多租户数据隔离:在多租户系统中,不同租户的数据需要进行隔离。通过 ThreadLocal 可以在不同函数或方法中传递租户标识,以确保数据隔离和正确处理。
  4. 全局上下文信息传递:有些全局的上下文信息,如请求ID、语言设置、时间区域等,可能需要在整个应用中共享。ThreadLocal 可以方便地将这些上下文信息传递给需要使用它们的函数或方法。
  5. 事务上下文传递:在使用事务管理时,事务上下文信息可能需要在多个函数或方法中传递。通过 ThreadLocal 可以将事务上下文与当前线程绑定,实现跨函数的事务传递。
  6. 日志追踪:在分布式系统中,日志追踪是常见的需求。通过在 ThreadLocal 中存储唯一的请求

下面我们通过一个 模拟传递Seesion的案例,来了解一下通过ThreadLocal跨函数传递数据

/**
 * 模拟ThreadLocal跨函数传递数据
 * 例如 我们一个方法接收从Http请求中获取的Session对象,然后在后续的方法中使用这个Session对象,这个时候就可以使用ThreadLocal
 * 自定义SessionHolder
 */
class SessionHolder {

	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	/**
	 * 存放Session的ThreadLocal实例
	 */
	private static final ThreadLocal<Session> sessionThreadLocal = new ThreadLocal<>();


	/**
	 * 这里我模拟从Http请求中获取Session对象
	 * @param session Session对象
	 */
	public static void setSession(Session session) {
		// 这里我们将Session对象放入ThreadLocal中
		// 随便对session的属性进行设置
		session.setStoreDir(new File("/tmp"));
		session.setTimeout(Duration.ofDays(30 * 60));
		session.setPersistent(true);
		sessionThreadLocal.set(session);
	}


	/**
	 * 获取Session对象
	 * @return Session对象
	 */
	public static Session getSession() {
		return sessionThreadLocal.get();
	}


	/**
	 * 移除ThreadLocal中的Session对象d
	 */
	public static void removeSession() {
		sessionThreadLocal.remove();
	}



	@Test
	@DisplayName("模拟发送Http请求,获取Session对象")
	public void test() {
		Session session = new Session();
		// 设置Session对象
		SessionHolder.setSession(session);
		// 在后续的方法中使用Session对象
		Session sessionInfo = SessionHolder.getSession();
		// 这里我们可以使用session1对象
		logger.error("获取的Session的StoreDir属性为:{}", sessionInfo.getStoreDir());
		// 这里我们使用完Session对象后,需要移除
		logger.error("移除Session对象!");
		SessionHolder.removeSession();
		// 再次获取
		Session sessionInfo2 = SessionHolder.getSession();
		logger.error("获取的Session的Timeout属性为:{}", sessionInfo2);
	}
}

当需要在不同方法中共享数据时,可以使用 ThreadLocal 来实现,避免重复的代码并保持逻辑清晰。

  • 创建一个静态的 ThreadLocal 实例 sessionThreadLocal,用于存放 Session 对象。
  • 在需要共享 Session 对象的方法中,使用 setSession() 方法将 Session 对象存储到 ThreadLocal 中。
  • 在其他方法中,使用 getSession() 方法获取存储在 ThreadLocal 中的 Session 对象。
  • 在合适的时机,调用 removeSession() 方法从 ThreadLocal 中移除 Session 对象。
  • 通过 ThreadLocal 实现了数据在不同方法间的传递和共享,避免了重复的代码,保持了逻辑的清晰性。

image-20240326211857285

问题?上述都在单线程环境下运行的,相当于同步的一个操作,那么如果在多线程并发的环境下运行的呢?还是我们想要的结果吗??下面我们通过一个案例来观察一下。

3.多线程(异步)下的ThreadLocal

	/**
	 * 多线程环境下的ThreadLocal
	 * @throws InterruptedException
	 */
	@Test
	@DisplayName("测试threadLocal在线程池中传递")
	public void test2() throws InterruptedException {

		ExecutorService executorService = Executors.newFixedThreadPool(2);
		// 放入threadLocal的值
		threadLocal.set("看看有没有被修改!");
		executorService.submit(() -> {
			logger.error("获取值为:{}", threadLocal.get());
			threadLocal.set("hello,my-thread-local-1");
			logger.error("当前值为:{}", threadLocal.get());
		});
		executorService.submit(() -> {
			logger.error("获取值为:{}", threadLocal.get());
			threadLocal.set("hello,my-thread-local-2");
			logger.error("当前值为:{}", threadLocal.get());

		});
		Thread.sleep(TimeUnit.SECONDS.toMillis(1));
		executorService.submit(() -> {
			logger.error("最终前值为:{}", threadLocal.get());
		});
		executorService.submit(() -> {
			logger.error("最终前值为:{}", threadLocal.get());
		});
		// 这里两个线程,首先原生的ThreadLocal 每个线程的TreadLocal都是单独占有一份的, 所以 pool-2-thread-1,pool-2-thread-2两个线程是不能获取到 main线程的ThreadLocal
		// 这里set的时候,会检查是当前线程是否在ThreadLocalMap中存储了,对当前引用的ThreadLocal值进行修改,没有存储在ThreadLocalMap进行创建
		// 注意 每个线程的 ThreadLocal都是【独立的】,【主线程】中的【子线程】无法获取主线程threadLocal
	}

image-20240326224637201

image-20240326224754751

上面这段主要实现了,模拟多线程环境下ThreadLocal的使用

  1. 在多线程环境下,每个线程都有自己的ThreadLocal实例,它们之间互不干扰。因此,pool-2-thread-1和pool-2-thread-2线程无法获取到主线程的ThreadLocal的值。

    • 我们从源代码的角度进行解释。

      1. ThreadLocal类的定义:

        public class ThreadLocal<T> {
            // ...
        }
        
      2. 每个Thread对象内部都有一个ThreadLocalMap实例,用于存储线程的本地变量。

        ThreadLocal.ThreadLocalMap threadLocals = null;
        
         
      3. ThreadLocalMap是一个定制的HashMap实现,它的键是ThreadLocal实例,值是对应线程的本地变量。如图所示
      
         ![image-20240326225458975](https://img-blog.csdnimg.cn/img_convert/6e4c67427bb6a422d1a76e8427dcd336.png)
      
         ```java
         static class ThreadLocalMap {
             static class Entry extends WeakReference<ThreadLocal<?>> {
                 /** The value associated with this ThreadLocal. */
                 Object value;
         
                 Entry(ThreadLocal<?> k, Object v) {
                     super(k);
                     value = v;
                 }
             }
             // ...........
         }
      
      1. 在ThreadLocal类中,通过getMap(Thread t)方法获取当前线程的ThreadLocalMap实例。

        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
        
      2. 在ThreadLocal类中,通过setInitialValue()方法设置初始值。

        protected T initialValue() {
            return null;
        }
        
      3. 当通过threadLocal.get()方法获取ThreadLocal的值时,会先获取当前线程的ThreadLocalMap实例,然后通过ThreadLocal实例作为键,在ThreadLocalMap中获取对应的Entry对象,最后返回Entry对象的值。

        public T get() {
            // 获取当前线程
            Thread t = Thread.currentThread();
            // 获取当前线程的ThreadLocalMap
            ThreadLocalMap map = getMap(t);
            // !! 如果ThreadLocalMap不为空,那么就获取当前ThreadLocal对象对应的Entry
            // !! Entry中存储了当前ThreadLocal对象和value
            // !! 其实Entry就是一个弱引用,key是ThreadLocal对象,value是value
            // !! 当ThreadLocal对象被回收的时候,Entry也会被回收
            // !! 但是多线程环境下,ThreadLocal对象被回收了,但是value没有被回收,这样就会导致内存泄漏
            // !! 因为value是强引用,这是ThreadLocal的一个缺点
            // !! 所以每次使用完ThreadLocal对象,需要调用remove方法,将ThreadLocal对象从ThreadLocalMap中移除
            if (map != null) {
                // 获取当前ThreadLocal对象对应的Entry
                ThreadLocalMap.Entry e = map.getEntry(this);
                // 如果Entry不为空,那么就返回Entry中的value
                if (e != null) {
                    // 强转为T
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            // 如果ThreadLocalMap为空,那么就创建一个ThreadLocalMap,并将当前ThreadLocal对象和value存入
            return setInitialValue();
        }
        
      4. 当通过threadLocal.set(value)方法设置ThreadLocal的值时,会先获取当前线程的ThreadLocalMap实例,然后将ThreadLocal实例和对应的值封装为Entry对象,并存储到ThreadLocalMap中。

        public void set(T value) {
            // 获取当前线程
            Thread t = Thread.currentThread();
            // 获取当前线程的ThreadLocalMap,如果为空那么就创建一个
            ThreadLocalMap map = getMap(t);
            // 如果获取的ThreadLocalMap不为空,那么就将当前ThreadLocal对象和value存入ThreadLocalMap
            if (map != null) {
                map.set(this, value);
            } else {
                // 如果获取的ThreadLocalMap空,那么就创建一个ThreadLocalMap 并将当前ThreadLocal对象和value存入
                createMap(t, value);
            }
        }
        
        
      5. 总结起来,ThreadLocal通过在每个Thread对象中维护一个ThreadLocalMap实例,实现了线程间的数据隔离。每个线程访问ThreadLocal时,通过获取自己的ThreadLocalMap实例,并使用ThreadLocal实例作为键,来获取或设置对应的值。这样就保证了每个线程都有自己独立的ThreadLocal实例,彼此之间互不干扰。

  2. 在每个任务中,通过threadLocal.get()方法获取ThreadLocal的值,并通过threadLocal.set()方法修改ThreadLocal的值。每个线程对ThreadLocal的修改是相互独立的,不会影响其他线程。

  3. 由于线程池中的任务是异步执行的,主线程需要等待一段时间,以确保所有任务都完成。这里使用Thread.sleep()方法暂停主线程的执行。

  4. 最后,提交了两个额外的任务,用于验证在主线程中是否能够获取到修改后的ThreadLocal的值。由于这两个任务是在主线程中执行的,它们可以访问到主线程的ThreadLocal的值。

ThreadLoacl 和 ThreadLocalMap之间的关系

image-20240326231508580

如果想要在多线程环境中,传递ThreadLocal值呢?下面有请InheritableThreadLocal登场

3.1.InheritableThreadLocal

InheritableThreadLocalThreadLocal的一个子类,它允许子线程继承父线程的线程局部变量的值。它提供了一种在父线程和子线程之间共享数据的机制。

当一个线程创建了一个子线程时,子线程会从父线程继承父线程的InheritableThreadLocal变量的值。这意味着子线程可以访问和修改父线程中设置的InheritableThreadLocal变量的值。

然而,需要注意的是,子线程修改InheritableThreadLocal变量的值不会影响到父线程的对应变量的值。子线程在继承父线程的InheritableThreadLocal值之后,它们在各自的线程上下文中操作该值,互不干扰。换句话说,虽然子线程可以访问和修改父线程的InheritableThreadLocal变量的值,但这些修改只在子线程的上下文中可见,不会影响到父线程的值。

这种设计是为了保持线程之间的数据隔离性,并且允许在并发环境中传递数据给子线程。父线程和子线程之间可以使用InheritableThreadLocal来传递上下文相关的数据,而不必显式地通过参数传递或者共享全局变量。

总结来说,InheritableThreadLocal允许子线程继承父线程的线程局部变量的值,但子线程对InheritableThreadLocal的修改不会影响到父线程的值。这种机制提供了一种在父线程和子线程之间共享数据的方式,同时保持了线程间的数据隔离性。

好了,了解完成 InheritableThreadLocal,那么我们来对上面的案例进行修改再来观察一下

	/**
	 * InheritableThreadLocal 使用 方便将父线程中数据传递给子线程
     */
	private static final ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
	/**
	 * InheritableThreadLocal,在线程池中传递值
	 * 如何获取主线 中的 ThreadLocal呢,这个时候 需要使用InheritableThreadLocal
	 *
	 * @throws InterruptedException
	 */
	@Test
	@DisplayName("测试InheritableThreadLocal在线程池中传递值")
	public void test3() throws InterruptedException {
		// 通过Executors.newFixedThreadPool()创建固定大小的线程池 2个线程
		ExecutorService executorService = Executors.newFixedThreadPool(2);
		// 放入threadLocal的值
		inheritableThreadLocal.set("看看有没有被修改!");

		// 创建两个异步任务,提交到线程池中
		executorService.submit(() -> {
			logger.error("获取值为:{}", inheritableThreadLocal.get());
			inheritableThreadLocal.set("hello,my-thread-local-1");
			logger.error("当前值为:{}", inheritableThreadLocal.get());
		});
		//
		Thread.sleep(TimeUnit.SECONDS.toMillis(1));
		executorService.submit(() -> {
			logger.error("获取值为:{}", inheritableThreadLocal.get());
			inheritableThreadLocal.set("hello,my-thread-local-2");
			logger.error("当前值为:{}", inheritableThreadLocal.get());
		});


		// 下面这两个线程主要用于获取值
		Thread.sleep(TimeUnit.SECONDS.toMillis(1));
		executorService.submit(() -> {
			logger.error("最终前值为:{}", inheritableThreadLocal.get());
		});
		executorService.submit(() -> {
			logger.error("最终前值为:{}", inheritableThreadLocal.get());
		});
		logger.error("最终前值为:{}", inheritableThreadLocal.get());

	}

这段代码演示了如何在线程池中使用InheritableThreadLocal传递值的过程。通过inheritableThreadLocal对象,在不同的线程中可以获取和修改共享的线程局部变量的值。每个任务都可以独立地访问和修改自己的inheritableThreadLocal值,而不会影响其他任务或线程的值。

  1. 首先,通过Executors.newFixedThreadPool()方法创建一个固定大小为2的线程池。

  2. 使用inheritableThreadLocal对象设置了一个值,即"看看有没有被修改!"

  3. 接下来,创建两个异步任务,并将它们提交到线程池中。

  4. 第一个任务中,通过inheritableThreadLocal.get()方法获取到之前设置的值,并打印输出。

  5. 然后,通过inheritableThreadLocal.set("hello,my-thread-local-1")方法修改了inheritableThreadLocal的值,并再次打印输出。

  6. 第二个任务的过程与第一个任务类似,同样获取并打印inheritableThreadLocal的值,然后修改并打印输出。

  7. 在两个任务提交后,通过Thread.sleep()方法等待1秒钟。

  8. 接着,又提交了两个任务,这两个任务主要用于获取最终的值。

  9. 最后,打印输出inheritableThreadLocal的值。

image-20240326232534067

通过结果我们可以得到以下结论

  • 子线程可以继承父线程的InheritableThreadLocal变量的值。
  • 子线程可以访问和修改父线程中设置的InheritableThreadLocal变量的值。
  • 子线程对InheritableThreadLocal的修改不会影响到父线程的值。
  • 这种机制提供了一种在父线程和子线程之间传递上下文相关数据的方式,同时保持线程间的数据隔离性。

这个时候,其实我们已经能正常的获取到主线程的threadLocal了,但是的呢,如果在线程池中存在父子线程,那么还能够正常获取到父线程的threadLocal吗?下面的再通过案例来了解一下

	@Test
	@DisplayName("测试InheritableThreadLocal在父子线程中传值")
	public void test3_1() throws InterruptedException {
		// 这里的创建了两个线程池 ,一个父亲线程池 一个儿子线程池
		ExecutorService executorService = Executors.newFixedThreadPool(2,new MyThreadFactory("father"));
		ExecutorService executorServiceSun = Executors.newFixedThreadPool(2,new MyThreadFactory("son"));
		// 放入threadLocal的值
		inheritableThreadLocal.set("看看有没有被修改!");

		// 创建两个父亲异步任务,提交到线程池中(并且父亲的线程池 中嵌套 儿子线程池)
		executorService.submit(() -> {
			logger.error("获取值为:{}", inheritableThreadLocal.get());
			inheritableThreadLocal.set("father,my-thread-local-1");
			logger.error("当前值为:{}", inheritableThreadLocal.get());
			executorServiceSun.submit(() -> {
				logger.error("获取值为:{}", transmittableThreadLocal.get());
				transmittableThreadLocal.set("son,my-thread-local-1");
				logger.error("当前值为:{}", transmittableThreadLocal.get());
			});
		});
		//
		Thread.sleep(TimeUnit.SECONDS.toMillis(1));
		executorService.submit(() -> {
			logger.error("获取值为:{}", inheritableThreadLocal.get());
			inheritableThreadLocal.set("father,my-thread-local-2");
			logger.error("当前值为:{}", inheritableThreadLocal.get());
			executorServiceSun.submit(() -> {
				logger.error("获取值为:{}", transmittableThreadLocal.get());
				transmittableThreadLocal.set("son,my-thread-local-2");
				logger.error("当前值为:{}", transmittableThreadLocal.get());
			});
		});


		// 下面这两个线程主要用于获取值
		Thread.sleep(TimeUnit.SECONDS.toMillis(1));
		executorService.submit(() -> {
			logger.error("最终前值为:{}", inheritableThreadLocal.get());
		});
		executorService.submit(() -> {
			logger.error("最终前值为:{}", inheritableThreadLocal.get());
		});
		logger.error("最终前值为:{}", inheritableThreadLocal.get());

	}

image-20240327075127088

  1. 这段创建了一个父线程池 executorService 和一个儿子线程池 executorServiceSun
  2. 在父线程中设置了 InheritableThreadLocal 的值为 “看看有没有被修改!”,然后提交了两个异步任务到父线程池 executorService 中。
  3. 每个异步任务都会打印出当前的 InheritableThreadLocal 的值,并将其设置为 “father,my-thread-local-1” 或 “father,my-thread-local-2”。
  4. 在每个异步任务中,提交了一个儿子线程池 executorServiceSun 中的异步任务。这意味着每个父线程的异步任务都提交了一个新的任务到儿子线程池中。
  5. 每个儿子线程池的异步任务尝试获取并打印出 InheritableThreadLocal 的值。但是,儿子线程池的任务获取到的值始终为 null,这是因为它们无法正确地继承父线程的

在 Java 中,InheritableThreadLocal 的值是在子线程创建时从父线程继承的。这意味着,只有当子线程被创建时,才会从父线程中继承 InheritableThreadLocal 的值。在您的代码中,子线程是在父线程的异步任务提交到儿子线程池之后才创建的。因此,在子线程创建时,它们无法正确地继承父线程的 InheritableThreadLocal 的值。 如何解决在不同线程池中,传递threadLocal呢?下面有请TransmittableThreadLocal登场!

3.2.TransmittableThreadLocal

引入 TransmittableThreadLocal

这个是Maven仓库的官方地址,可以在这里引入
Maven Repository: Search/Browse/Explore (mvnrepository.com)

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.14.5</version>
</dependency>

// https://mvnrepository.com/artifact/com.alibaba/transmittable-thread-local
implementation 'com.alibaba:transmittable-thread-local:2.14.5'

TransmittableThreadLocal(简称 TTL)是阿里巴巴开源的一个线程上下文传递工具,它能够解决在复杂的线程池环境中传递值的问题。与 Java 标准库提供的 ThreadLocal 不同,TransmittableThreadLocal 提供了更加灵活和强大的功能,特别是在使用线程池时。

  1. TransmittableThreadLocal相比ThreadLocal有以下几个优势:

    1. 跨线程传递:TransmittableThreadLocal可以在线程池中传递变量的值,确保在任务切换线程的过程中,仍然能够获取到正确的变量值。而ThreadLocal在线程切换时无法正确传递变量值。

    2. 线程池适用:TransmittableThreadLocal特别适用于使用线程池执行任务的场景。在线程池中,任务的执行可能会在不同的线程中切换,而TransmittableThreadLocal能够保证在任务切换时传递变量值,确保任务在任何线程中都能够获得正确的变量值。

    3. 线程安全性:TransmittableThreadLocal在实现上考虑了线程安全性。它使用了一种特殊的策略,保证了在并发环境下,每个线程都可以独立地访问和修改自己的变量值,而不会影响其他线程的变量值。

    4. 扩展支持:TransmittableThreadLocal是对ThreadLocal的扩展和增强,提供了更加灵活和可靠的线程本地变量的管理方式。它通过重写线程池的execute()方法,实现了变量值的传递,兼容了原有的ThreadLocal使用方式。

    TransmittableThreadLocal相比ThreadLocal在跨线程传递、线程池适用、线程安全性和扩展支持等方面具有明显的优势,特别适合在使用线程池执行任务的场景中使用。

下面我们来改写一下上面的案例

	/**
	 * TransmittableThreadLocal(TTL)用法
	 * 使用 transmittableThreadLocal解决线程池中传递值的问题,
	 * 但是每次的ThreadLocal我只想针对于当次操作,下次操作我还是想想使用原来的值,这个时候就可以使用TransmittableThreadLocal
	 * 通过TtlExecutors.getTtlExecutorService()装饰线程池,实现线程池中传递值,但是不会影响主线程的ThreadLocal
	 * 当线程发生切换时,其实再次获取的值是之前的值,不会受到影响。
	 *
	 * @throws InterruptedException
	 */
	@Test
	@DisplayName("测试transmittableThreadLocal")
	public void test4() throws InterruptedException {
		// 放入threadLocal的值
		transmittableThreadLocal.set("看看有没有被修改!");
		ExecutorService executorService = Executors.newFixedThreadPool(2);
		ExecutorService executorServiceSun = Executors.newFixedThreadPool(2);
		// 使用 TtlExecutors.getTtlExecutorService() 装饰线程池
		executorService = TtlExecutors.getTtlExecutorService(executorService);
        // 注意 这里我们并没有对son线程池进行额外的修饰,因为它不需要传递了,它只是需要能接收到父线程的ThreadLocal即可

	

		executorService.submit(() -> {
			logger.error("获取值为:{}", transmittableThreadLocal.get());
			transmittableThreadLocal.set("father,my-thread-local-1");
			logger.error("当前值为:{}", transmittableThreadLocal.get());
			executorServiceSun.submit(() -> {
				logger.error("获取值为:{}", transmittableThreadLocal.get());
				transmittableThreadLocal.set("sum,my-thread-local-2");
				logger.error("当前值为:{}", transmittableThreadLocal.get());
			});
		});
		executorService.submit(() -> {
			logger.error("获取值为:{}", transmittableThreadLocal.get());
			transmittableThreadLocal.set("father,my-thread-local-2");
			logger.error("当前值为:{}", transmittableThreadLocal.get());

			executorServiceSun.submit(() -> {
				logger.error("获取值为:{}", transmittableThreadLocal.get());
				transmittableThreadLocal.set("sum,my-thread-local-2");
				logger.error("当前值为:{}", transmittableThreadLocal.get());
			});
		});


		// 在线程池 另外在开辟新的线程
		executorService.submit(() -> {
			logger.error("最终前值为:{}", transmittableThreadLocal.get());
		});
		executorService.submit(() -> {
			logger.error("最终前值为:{}", transmittableThreadLocal.get());
		});
		logger.error("最终前值为:{}", transmittableThreadLocal.get());

	}

image-20240327080249499

3.3.总结

以下是对ThreadLocal、InheritableThreadLocal和TransmittableThreadLocal的描述,以及它们各自的优势和缺点总结

ThreadLocal:
ThreadLocal是Java中的一个线程本地变量工具类。它允许在每个线程中创建独立的变量副本,每个线程都可以独立地对其进行读写操作,互不干扰。

优势:

  • 线程隔离:每个线程都有自己的变量副本,线程之间互不干扰,避免了线程安全问题。
  • 简单易用:使用ThreadLocal可以方便地在当前线程中存储和获取变量的值。

缺点:

  • 无法跨线程传递:在使用线程池执行任务时,任务可能会在不同的线程中切换,ThreadLocal无法正确传递变量值,导致在某些情况下无法获取到正确的值。

InheritableThreadLocal:
InheritableThreadLocal是ThreadLocal的一个子类,它解决了ThreadLocal无法跨线程传递变量值的问题。

优势:

  • 跨线程传递:InheritableThreadLocal可以在线程之间传递变量值,子线程可以继承父线程的变量值。
  • 简单易用:使用方式与ThreadLocal类似,通过set()和get()方法来存储和获取变量的值。

缺点:

  • 线程安全问题:在并发环境下,多个线程同时修改InheritableThreadLocal的变量值可能会引发线程安全问题,需要进行额外的同步操作。

TransmittableThreadLocal:
TransmittableThreadLocal是对ThreadLocal的扩展,特别适用于在线程池中传递变量值的场景。它通过重写线程池的execute()方法,在任务切换时传递变量值,确保任务在任何线程中都能够获得正确的值。

优势:

  • 跨线程传递:TransmittableThreadLocal可以在线程池中传递变量值,保证任务切换时仍能获取正确的值。
  • 线程池适用:特别适用于使用线程池执行任务的场景,解决了ThreadLocal无法跨线程传递的问题。
  • 线程安全性:在实现上考虑了线程安全性,采用特殊的策略保证并发访问时的线程安全。

缺点:

  • 对线程池的依赖:TransmittableThreadLocal需要通过重写线程池的execute()方法才能实现变量值的传递,对线程池的使用有一定的依赖性。

综上所述,ThreadLocal适用于线程隔离场景,但无法跨线程传递;InheritableThreadLocal解决了跨线程传递的问题,但可能引发线程安全问题;TransmittableThreadLocal在线程池场景中使用,可以跨线程传递变量值并考虑线程安全性,但对线程池的依赖较高。选择使用哪种类取决于具体的应用场景和需求。

4.ThreadLocal源码解读

ThreadLocal 是 Java 中的一个类,用于创建线程局部变量。线程局部变量是一种特殊的变量,每个线程都拥有自己独立的变量副本,线程之间的修改互不影响。

使用 ThreadLocal 可以将变量与线程关联起来,每个线程都可以独立地访问和修改自己的变量副本,而不会影响其他线程的变量副本。它在多线程编程中常用于保持线程安全性,避免线程之间共享状态的问题。

以下是 ThreadLocal 的一些重要方法和操作:

  • set(T value): 将指定的值设置为当前线程的变量副本。

  • get(): 获取当前线程的变量副本的值。

  • remove(): 移除当前线程的变量副本。

  • initialValue(): 返回变量的初始值。可以通过继承 ThreadLocal 并重写该方法来定制变量的初始值。

  • withInitial(Supplier<? extends T> supplier): 使用提供的 Supplier 接口定义变量的初始值。

ThreadLocal 的典型用法是在多线程环境下,将需要线程独立访问的数据存储在 ThreadLocal 对象中。每个线程通过 ThreadLocal 对象进行数据的存取操作,保证了线程之间的数据隔离,避免了线程安全问题。

需要注意的是,使用 ThreadLocal 时要注意内存泄漏的问题。由于 ThreadLocal 使用的是弱引用来持有线程局部变量的副本,如果在使用完 ThreadLocal 后没有及时清理和移除,就可能导致内存泄漏。因此,必须在使用完 ThreadLocal 后调用 remove() 方法将其从当前线程中移除,以释放对应的变量副本。

另外,ThreadLocal 并不是用来解决并发访问共享变量的问题的,它只是提供了一种线程局部存储的机制。在需要解决并发访问共享变量的问题时,仍然需要使用其他的同步机制,如锁或并发集合类。

ThreadLocal提供的源码 并不是很多,下面我们一一来介绍一下这些方法

4.1.set(T value)方法

ThreadLocal的set()方法用于将当前线程的ThreadLocal变量与指定的值关联起来。

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取当前线程的ThreadLocalMap,如果为空那么就创建一个
        ThreadLocalMap map = getMap(t);
        // 如果获取的ThreadLocalMap不为空,那么就将当前ThreadLocal对象和value存入ThreadLocalMap
        if (map != null) {
            // 将value绑定到当前的ThreadLocal实例上
            map.set(this, value);
        } else {
            // 如果当前线程没有ThreadLocalMap
            // 那么就创建一个ThreadLocalMap,然后作为成员变量绑定到当前线程上(Thread)
            createMap(t, value);
        }
    }



    /**
      * Get the map associated with a ThreadLocal. Overridden in
      * InheritableThreadLocal.
      * 获取线程t的ThreadLocalMap成员变量
      * @param  t the current thread
      * @return the map
      */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }


    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     * !! 线程t创建一个ThreadLocalMap,并将当前ThreadLocal对象和value存入
     * !! 并且为新的Map成员变量设置第一个Entry Key-Value键值对,Key就是当前线程的ThreadLocal对象,Value就是当前线程的value
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

ThreadLocal的set()方法用于将当前线程的ThreadLocal变量与指定的值关联起来。其中set()方法的主要流程就是:

  1. 获取当前线程:通过Thread.currentThread()方法获取当前线程的引用。

  2. 获取当前线程的ThreadLocalMap:调用getMap(Thread t)方法获取当前线程的ThreadLocalMap对象。getMap()方法是一个私有方法,用于返回线程t的ThreadLocalMap成员变量。

  3. 检查ThreadLocalMap是否存在:如果获取的ThreadLocalMap不为空,则表示当前线程已经有一个ThreadLocalMap对象。

  4. 存储ThreadLocal和值:如果ThreadLocalMap存在,将当前ThreadLocal对象和指定的值存储到ThreadLocalMap中,调用map.set(this, value)方法实现。

  5. 创建ThreadLocalMap:如果ThreadLocalMap不存在,则需要创建一个新的ThreadLocalMap对象,并将其作为当前线程的成员变量。调用createMap(Thread t, T firstValue)方法实现。

  6. 设置初始键值对:在新创建的ThreadLocalMap中设置第一个Entry键值对,其中键是当前ThreadLocal对象,值是指定的值。

4.2.get()方法

get() 方法用于获取当前线程的ThreadLocal变量的值。

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        // !! 如果ThreadLocalMap不为空,那么就获取当前ThreadLocal对象对应的Entry,Entry中存储了当前ThreadLocal对象和value
        // !! 其实Entry就是一个弱引用,key是ThreadLocal对象,value是value,当ThreadLocal对象被回收的时候,Entry也会被回收
        // !! 但是多线程环境下,ThreadLocal对象被回收了,但是value没有被回收,这样就会导致内存泄漏,因为value是强引用,这是ThreadLocal的一个缺点
        // !! 所以每次使用完ThreadLocal对象之后,需要调用remove方法,将ThreadLocal对象从ThreadLocalMap中移除
        if (map != null) {
            // 获取当前ThreadLocal对象对应的Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 如果Entry不为空,那么就返回Entry中的value
            if (e != null) {
                // 强转为T
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 如果ThreadLocalMap为空,那么就创建一个ThreadLocalMap,并将当前ThreadLocal对象和value存入
        return setInitialValue();
    }



   /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     * 设置ThreadLocal关联的初始值,并返回初始值
     * @return the initial value
     */
    private T setInitialValue() {
        // 调用初始化钩子函数,和获取初始值
        T value = initialValue();
        // 获取当前线程 t
        Thread t = Thread.currentThread();
        // 获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        // 如果ThreadLocalMap不为空,那么就将当前ThreadLocal对象和value存入ThreadLocalMap
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
        // 如果是终结线程本地变量,那么就注册
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        // 返回初始值
        return value;
    }

下面是 get() 方法的简要流程:

  1. 获取当前线程:通过 Thread.currentThread() 方法获取当前线程的引用。

  2. 获取当前线程的 ThreadLocalMap:调用 getMap(Thread t) 方法获取当前线程的 ThreadLocalMap 对象。

  3. 检查 ThreadLocalMap:如果获取的 ThreadLocalMap 不为空,则表示当前线程已经有一个 ThreadLocalMap 对象。

  4. 获取 ThreadLocal 对应的值:如果 ThreadLocalMap 存在,通过调用 map.getEntry(this) 方法获取当前 ThreadLocal 对象对应的 Entry。Entry 中存储了当前 ThreadLocal 对象和其关联的值。

  5. 返回值:如果 Entry 不为空,即当前线程的 ThreadLocalMap 中存在该 ThreadLocal 对象的值,则将其值返回。

  6. 创建并设置初始值:如果 ThreadLocalMap 为空,表示当前线程还没有与 ThreadLocal 关联的值。调用 setInitialValue() 方法来设置初始值,并将其与当前线程关联。

  7. 返回初始值:返回设置的初始值。

需要注意的是,在多线程环境下,如果 ThreadLocalMap 中的 ThreadLocal 对象被回收,但是其关联的值仍然存在,可能会导致内存泄漏。因此,在使用完 ThreadLocal 对象后,应调用 remove() 方法将其从 ThreadLocalMap 中移除。

setInitialValue() 方法是一个私有方法,用于设置 ThreadLocal 的初始值,并将其与当前线程关联。在设置初始值之前,还会调用 initialValue() 方法获取初始值。如果 ThreadLocalMap 为空,会创建一个新的 ThreadLocalMap,并将 ThreadLocal 对象和初始值存入其中。如果 ThreadLocal 是终结线程本地变量的子类,则会进行注册。

综上所述,get() 方法用于获取当前线程的 ThreadLocal 变量的值,如果不存在则会设置初始值,并返回相应的值。

4.3.remove()方法

    /**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     * remove方法的核心就是将当前ThreadLocal对象从ThreadLocalMap中移除,这样就可以避免内存泄漏
     * @since 1.5
     */
     public void remove() {
         // 从ThreadLocalMap中先获取当前的线程的ThreadLocalMap
         ThreadLocalMap m = getMap(Thread.currentThread());
         // 如果ThreadLocalMap不为空,那么就将当前ThreadLocal对象从ThreadLocalMap中移除
         if (m != null) {
             m.remove(this);
         }
     }

4.4.initialValue()方法

    /**
     * !! 当 ThreadLocal 在当前ThreadLocalMap中为绑定值的时候,initialValue方法会被调用 用于获取初始值
     * !! 一般情况下,我们在创建时,可以指定其初始值,如果没有指定,那么就会调用initialValue方法获取初始值
     * !! 用法 ThreadLocal.withInitial(() -> 1);
     */
    protected T initialValue() {
        return null;
    }

    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

5.TreadLocalMap源码解读

ThreadLocalMapThreadLocal 类的内部类,用于存储线程局部变量的映射关系。每个线程都有一个对应的 ThreadLocalMap 对象,它以 ThreadLocal 对象作为键,以线程局部变量的值作为值。

ThreadLocalMap 使用线性探测法实现,它是一个哈希表,内部使用数组存储键值对。每个键值对存储在数组的一个位置上,如果发生哈希冲突,则会线性探测到下一个可用位置。在 ThreadLocalMap 中,键是 ThreadLocal 对象,值是与之对应的线程局部变量的值。

以下是 ThreadLocalMap 的一些关键方法和操作:

  • set(ThreadLocal<?> key, Object value): 将指定的 ThreadLocal 对象和对应的值存储到 ThreadLocalMap 中。

  • getEntry(ThreadLocal<?> key): 根据指定的 ThreadLocal 对象获取对应的键值对 Entry

  • remove(ThreadLocal<?> key): 移除 ThreadLocalMap 中与指定 ThreadLocal 对象关联的键值对。

  • expungeStaleEntry(int staleSlot): 移除过期的键值对,即 keyvalue 已经被回收的键值对。

  • replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot): 替换过期的键值对,即更新键为 key 的键值对的值。

ThreadLocalMap 是为了解决线程安全问题而设计的,在多线程环境下,每个线程都有自己独立的 ThreadLocalMap 实例,避免了线程之间的数据混乱。但是需要注意,如果使用 ThreadLocal 时没有及时清理和移除,就可能导致内存泄漏问题,因为 ThreadLocalMap 中的键是使用弱引用持有的,而值是使用强引用持有的,如果 ThreadLocal 对象被垃圾回收,但是对应的值没有被清理和移除,就会导致值无法释放的内存泄漏问题。因此,在使用完 ThreadLocal 后,应调用 remove() 方法将其从 ThreadLocalMap 中移除,以避免内存泄漏的发生。

5.1.ThreadLocalMap的主要成员变量

ThreadLocalMap 类作为 ThreadLocal 的内部类,包含了以下主要成员变量:

  1. Entry[] table:一个 Entry 类型的数组,用于存储键值对。EntryThreadLocalMap 的内部类,表示一个键值对。

  2. int sizeThreadLocalMap 中当前存储的键值对数量。

  3. int threshold:表示 ThreadLocalMap 的阈值,当 size 超过阈值时,会触发扩容操作。

  4. int count:表示 ThreadLocalMap 的实际容量,即 table 数组的长度。

  5. int modCount:记录 ThreadLocalMap 结构被修改的次数,用于在迭代过程中进行快速失败检查。

  6. int nextIndex:用于支持 set() 操作的索引,指示下一个要插入的位置。

这些成员变量一起组成了 ThreadLocalMap 的内部数据结构,用于存储和管理线程局部变量的键值对。table 数组存储了实际的键值对数据,sizethreshold 用于控制 ThreadLocalMap 的容量和扩容,count 记录当前容量,modCount 用于快速失败检查,nextIndex 用于支持 set() 操作。

 static class ThreadLocalMap {

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

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

        /**
         * !! Map的初始容量 默认 16 和HashMap一样
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * !! Map的条目数组 主要用于哈希表
         */
        private Entry[] table;


        /**
         * !! Map的条目数量
         */
        private int size = 0;

        /**
         * !! Map的阈值(扩容银子)
         */
        private int threshold; // Default to 0

        /**
         * !! 构造方法 主要用于设置阈值 每次扩容的时候都会重新计算阈值 容量的2/3时候扩容
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * !! 获取下一个索引
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * !! 获取上一个索引
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        /**
         * !! 构造方法
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            // 初始化table数组
            table = new Entry[INITIAL_CAPACITY];
            // 计算当前ThreadLocal对象的哈希值
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            // 将当前ThreadLocal对象和value存入table数组
            table[i] = new Entry(firstKey, firstValue);
            // 设置size为1
            size = 1;
            // 设置阈值
            setThreshold(INITIAL_CAPACITY);
        }
  // ..........
 }

5.2.set(ThreadLocal<?> key, Object value)

作为参考这里,我们只针对set方法做一些简单的解读

 /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            // !! 获取当前ThreadLocal对象的哈希值
            Entry[] tab = table;
            // !! 获取table的长度
            int len = tab.length;


            // !! 根据Key的哈希值计算出在table数组中的位置
            int i = key.threadLocalHashCode & (len-1);

            // !! 遍历table数组,找到key对应的Entry
            // !! 槽点:就是table数组中的一个位置,这个位置可能是空的,也可能有Entry
            // !! 这里从槽点i开始,一直循环到找到key对应的Entry或者找到一个空槽点
            // !! 注意,如果没有发现槽点,那么必定会找到一个空槽点,没有空间会自动进行扩容的
            // !! 如果找到了key对应的Entry,那么就将value设置到Entry中
            for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
                /// !! 获取Entry中的key(找到现有槽点)
                ThreadLocal<?> k = e.get();
                // !! 如果key相等,那么就将value设置到Entry中
                if (k == key) {
                    e.value = value;
                    return;
                }
                // !! 如果找了异常的槽点,槽点会被GC回收,那么就将Entry设置为null
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            // !! 如果没有找到现有的槽点,那么就将value设置到新的槽点中
            tab[i] = new Entry(key, value);
            // !! 设置ThreadLocal的数量
            // !! 如果ThreadLocal的数量大于阈值,那么就进行扩容
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

以下是关于 set() 方法的简单概要

  1. 线程(Thread)调用 ThreadLocalset(key, value) 方法。
  2. ThreadLocal 对象将调用 ThreadLocalMapset(key, value) 方法。
  3. ThreadLocalMap 获取 table 数组和长度 len
  4. 根据 ThreadLocal 对象的哈希值计算在 table 数组中的位置 i
  5. ThreadLocalMap 遍历 table 数组,查找对应的 Entry
  6. 如果找到现有的槽点(Entry),则将值设置到该 Entry 中。
  7. 如果找到异常的槽点(Entry 的 ThreadLocal 对象为 null),则使用 replaceStaleEntry(key, value, i) 方法替换键值对。
  8. 如果没有找到现有的槽点,则创建新的 Entry,将 ThreadLocal 和值存入其中。
  9. 增加 ThreadLocalMap 的大小 size,并根据需要进行清理和扩容。

下面时set的一个简单的时序图

ThreadLocalMap ThreadLocal Entry Thread set(key, value) set(key, value) 获取table和len 计算哈希值,找到位置i 遍历table数组 获取Entry对象e get() 返回ThreadLocal对象k == 设置value 创建新的Entry 增加size 清理和扩容 alt [找到现有的槽点] [没有找到现有的槽点] ThreadLocalMap ThreadLocal Entry Thread
  • 13
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值