Java中的ThreadLocal详解
作者:菜鸟小窝
一、ThreadLocal
1、说明
ThreadLocal 是一个线程局部变量。其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
2、使用方法
ThreadLocal<String> nameThreadLocal = new ThreadLocal<>(); nameThreadLocal.set(name); nameThreadLocal.get();
3、Request 使用案例
- 在controller中注入的request是jdk动态代理对象,ObjectFactoryDelegatingInvocationHandler的实例.当我们调用成员域request的方法的时候其实是调用了objectFactory的getObject()对象的相关方法.这里的objectFactory是RequestObjectFactory
- RequestObjectFactory的getObject其实是从RequestContextHolder的threadlocal中去取值的
- 请求刚进入springmvc的dispatcherServlet的时候会把request相关对象设置到RequestContextHolder的threadlocal中去
二、InheritableThreadLocal
1、说明
ThreadLocal设计之初就是为了绑定当前线程,如果希望当前线程的ThreadLocal能够被子线程使用,实现方式就会相当困难(需要用户自己在代码中传递)。在此背景下,InheritableThreadLocal应运而生。
2、原理
创建新的线程时,会调用以下的构造方法。
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
我们沿着构造方法中的init方法,可以找到这样一段代码。就是在这里创建的 inheritableThreadLocals 。
if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
继续查看 createInheritedMap 方法,里面新建了一个 ThreadLocalMap 对象,其构造方法如下。
分析代码我们可知,在创建子线程时,会将父线程的 inheritableThreadLocals 复制到子线程的 inheritableThreadLocals 对象。
private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }
三、TransmittableThreadLocal
1、概述
使用线程池时,线程会被复用,因此线程池中的线程,执行任务时如果要获取 提交线程(即 提交任务到线程池 的线程) 保存的对象,则可以使用 TransmittableThreadLocal 。
2、使用方法
TransmittableThreadLocal<String> ttl = new TransmittableThreadLocal<>(); ThreadPoolExecutor executor = new ThreadPoolExecutor(ThreadSize, ThreadSize, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10)); // 设置变量,并将任务提交到线程池 ttl.set("ThreadA-TTL"); Runnable task = new Runnable() { @Override public void run() { System.out.println(ttl.get()); } }; // 生成修饰后的对象ttlRunnable。 task = TtlRunnable.get(task); executor.submit(task);
3、案例(业务侵入式)
(1)代码
package com.scy.example.controller; import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.TtlRunnable; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class Test02 { static ThreadPoolExecutor executor = null; static TransmittableThreadLocal<String> ttl = new TransmittableThreadLocal<>(); public static void main(String[] args) { try { // 在A线程中创建线程池,并且启动线程池中的线程 Thread threadA = new Thread(() -> { int ThreadSize = 1; executor = new ThreadPoolExecutor(ThreadSize, ThreadSize, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10)); ttl.set("ThreadA-TTL"); // 启动线程池中的线程 for (int i = 0; i < ThreadSize; i++) { executor.submit(() -> { System.out.println("初始化线程完毕!"); }); } }); threadA.start(); // 等待线程池中的线程启动完毕 Thread.sleep(100); // 在B线程中,向线程池提交任务 Thread threadB = new Thread(() -> { ttl.set("ThreadB-TTL"); Runnable task = new Runnable() { @Override public void run() { System.out.println(ttl.get()); } }; // 额外的处理,生成修饰了的对象ttlRunnable task = TtlRunnable.get(task); executor.submit(task); }); threadB.start(); Thread.sleep(100); executor.shutdown(); } catch (InterruptedException e) { e.printStackTrace(); } } }
(2)结果
初始化线程完毕!
ThreadB-TTL
(3)说明
使用 TransmittableThreadLocal 存储对象时;
如果任务没有使用 TtlRunnable 修饰,则 TransmittableThreadLocal 相当于 InheritableThreadLocal ;此时在线程池中执行任务时获取对象,获取的是 创建线程(即 创建线程池中线程 的线程) 保存的对象。
如果任务使用了 ttlRunnable 修饰,此时在线程池中执行任务时获取对象,获取的是 提交线程(即 提交任务到线程池 的线程) 保存的对象。而且无法获取创建线程保存的对象。
4、案例(agent实现)
需配置启动参数 -javaagent:path/to/transmittable-thread-local-2.x.x.jar
-javaagent:D:\maven\mvnRespo\com\alibaba\transmittable-thread-local\2.13.2\transmittable-thread-local-2.13.2.jar
四、TaskDecorator
1、概述
使用线程池时,线程会被复用,因此线程池中的线程,执行任务时如果要获取 提交线程(即 提交任务到线程池 的线程) 保存的对象,还可以使用 TaskDecorator 。
看这个名称大概就能猜出是一个装饰器设计原理。
spring 4.3 提供 TaskDecorator
2、使用方法
主线程16个,子线程2个,执行10次,目的是尽可能让子线程复用。
public class TaskDecoratorTest { public static void main(String[] args) { new TaskDecoratorTest().testThreadLocal(); } public void testThreadLocal() { ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); ExecutorService mainThreadPool = Executors.newFixedThreadPool(16); ThreadPoolTaskExecutor childThreadPool = new ThreadPoolTaskExecutor(); childThreadPool.setCorePoolSize(2); childThreadPool.setMaxPoolSize(2); childThreadPool.setTaskDecorator(runnable -> { int v = threadLocal.get(); System.out.println("装饰器中获取到主线程=" + Thread.currentThread().getName() + " 获取上下文=" + v); return () -> { try { //重新copy传递给子线程 threadLocal.set(v); runnable.run(); } finally { threadLocal.remove(); } }; }); childThreadPool.initialize(); for (int i = 0; i < 10; i++) { int finalI = i; mainThreadPool.execute(() -> { //模拟在主线程设置上下文变量 threadLocal.set(finalI); childThreadPool.execute(() -> System.out.println("子线程" + Thread.currentThread().getName() + " 获取上下文变量=" + threadLocal.get())); threadLocal.remove(); }); } try { childThreadPool.getThreadPoolExecutor().awaitTermination(3, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } }
3、原理
@Override protected ExecutorService initializeExecutor( ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) { BlockingQueue<Runnable> queue = createQueue(this.queueCapacity); ThreadPoolExecutor executor; //当线程池的装饰器不为空时,执行execute方法会进入这里,因为它重写了execute方法 if (this.taskDecorator != null) { executor = new ThreadPoolExecutor( this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler) { //这里是一个代理设计模式的实现,对execute做了一层代理 @Override public void execute(Runnable command) { //执行装饰器的逻辑,注意这段代码是在主线程中运行 Runnable decorated = taskDecorator.decorate(command); if (decorated != command) { decoratedTaskMap.put(decorated, command); } //子线程真正执行的方法(异步模块)...初始化核心线程数,核心线程满了入队列,队列满开启至最大线程数 super.execute(decorated); } }; } else { executor = new ThreadPoolExecutor( this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler); } if (this.allowCoreThreadTimeOut) { executor.allowCoreThreadTimeOut(true); } this.threadPoolExecutor = executor; return executor; }
到此这篇关于Java中的ThreadLocal详解的文章就介绍到这了,更多相关ThreadLocal详解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!