写作目录
简单分析三种ThreadLocal并对三种ThreadLocal做比较
ThreadLocal
ThreadLocal使用场景
ThreadLocal是和当前线程绑定在一起的,就是在一开始的设置一个值,然后在后面可以获得这个值。
ThreadLocal使用案例
public class ThreadLocalDemo { public static ThreadLocal threadLocal = new ThreadLocal(); public static void main(String[] args) { // 在当前线程中设置了一个数据 threadLocal.set("userId:zhangsan"); // 在后面都可以取到 service(); // 最后别忘记移除 threadLocal.remove(); } public static void service() { Object o = ThreadLocalDemo.threadLocal.get(); System.out.println("service...:" + o); dao(); } public static void dao() { Object o = ThreadLocalDemo.threadLocal.get(); System.out.println("dao...:" + o); } }
ThreadLocal面试题
https://zhuanlan.zhihu.com/p/140433592
ThreadLocal的问题
thread只能在本线程中传参数,但是向下面这种情况下父子进程就不能实现数据的传递
ThreadLocal threadLocal = new ThreadLocal(); // 在当前线程中设置了一个数据 threadLocal.set("userId:zhangsan"); new Thread( () -> { System.out.println("new thread:"+threadLocal.get()); }, "name") .start();
InheritableThreadLocal
使用案例:父线程设置数据,子线程可以拿到
InheritableThreadLocal threadLocal = new InheritableThreadLocal(); threadLocal.set(1); new Thread( () -> { System.out.println(threadLocal.get()); }, "name") .start();
数据复制原理
其实是在Thread的构造方法里进行的复制
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { //拿到父线程 Thread parent = currentThread(); //把父线程里的inheritableThreadLocals当做参数传入 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); }
InheritableThreadLocal鸡肋的地方
只能在线程创建的时候赋值一次,后面父线程修改了数据,子线程拿不到,现在都是线程池了,谁没事创建线程啊
InheritableThreadLocal threadLocal = new InheritableThreadLocal(); threadLocal.set(1); System.out.println("父线程拿到的数据:"+ threadLocal.get()); new Thread( () -> { while (true){ try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } finally { } System.out.println("子线程拿到的数据:"+ threadLocal.get()); } }, "child-01") .start(); threadLocal.set(2); System.out.println("父线程拿到的数据:"+ threadLocal.get());
TransmittableThreadLocal
使用案例
父线程设置的值子线程可以拿到,而且也适用与线程池
<!-- https://mvnrepository.com/artifact/com.alibaba/transmittable-thread-local --> <dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.11.5</version> </dependency>
package threadlocal; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.threadpool.TtlExecutors; /** */ public class TransmittableThreadLocalDemoMe { public static void main(String[] args) { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 1, 1, 20L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); // 需要注意的是,使用TTL的时候,要想传递的值不出问题,线程池必须得用TTL加一层代理(下面会讲这样做的目的) ExecutorService executorService = TtlExecutors.getTtlExecutorService(threadPoolExecutor); // 这里采用TTL的实现 TransmittableThreadLocal tl = new TransmittableThreadLocal<>(); String mainThreadName = "main"; tl.set("1");//父线程,第一次赋值 executorService.submit( () -> { sleep(1L); System.out.println( String.format( "本地变量改变之前(1), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), tl.get())); }); sleep(2000L); // 确保上面的会在tl.set执行之前执行 System.out.println("sleep"); tl.set(2); // 等上面的线程池第一次启用完了,父线程再给自己赋值 executorService.execute( () -> { sleep(1L); System.out.println( String.format( "本地变量改变之后(2), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), tl.get())); }); } private static void sleep(long time) { try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } }
父子传值原理
首先,ThreadPool线程池需要包装一层
// 需要注意的是,使用TTL的时候,要想传递的值不出问题,线程池必须得用TTL加一层代理(下面会讲这样做的目的) ExecutorService executorService = TtlExecutors.getTtlExecutorService(threadPoolExecutor);
看TtlExecutorService的submit方法
@Override public Future<?> submit(@NonNull Runnable task) { return executorService.submit(TtlRunnable.get(task)); }
一直跟下去,就发现其实Runable也经过包装为TtlRunable
private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { //保存threadlocal里的值,其实就是一个快照 this.capturedRef = new AtomicReference<Object>(capture()); this.runnable = runnable; this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun; }
在上面中在capture中保存的数据,先通过replay方法执行设置数据,然后执行业务逻辑,然后通过restore执行relpay的反向操作,然后使当前线程很干净。
@Override public void run() { //拿到保存的快照信息 Object captured = capturedRef.get(); if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) { throw new IllegalStateException("TTL value reference is released after run!"); } //把captured数据添加到threadlocal中 Object backup = replay(captured); try { runnable.run(); } finally { //replay(captured) 的反向操作 restore(backup); } }
未解决的问题
其实TransmittableThreadLocal和InheritableThreadLocal有一个明显不同的地方是InheritableThreadLocal在创建Thread的时候执行复制或者赋值操作,但是TransmittableThreadLocal是在创建Runable(TtlRunable)进行复制或者赋值操作。一旦Runable创建成功,在父线程中修改数据,Runable还是感知不到数据的变化。
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 1, 1, 20L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); // 需要注意的是,使用TTL的时候,要想传递的值不出问题,线程池必须得用TTL加一层代理(下面会讲这样做的目的) ExecutorService executorService = TtlExecutors.getTtlExecutorService(threadPoolExecutor); // 这里采用TTL的实现 TransmittableThreadLocal tl = new TransmittableThreadLocal<>(); tl.set(LocalDateTime.now().toString()); executorService.submit( () -> { while (true) { sleep(1000L); System.out.println( String.format( "线程名称-%s, 变量值=%s", Thread.currentThread().getName(), tl.get())); } }); while (true) { sleep(1000L); //父线程设置值 tl.set(LocalDateTime.now().toString()); }