ThreadLocal笔记

简介: ThreadLocal笔记

写作目录


 简单分析三种ThreadLocal并对三种ThreadLocal做比较


微信截图_20230224230030.png


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();


9.png


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());


10.png


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();
        }
    }
}


11.png


父子传值原理


首先,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());
        }
目录
相关文章
|
6月前
|
存储 安全 Java
面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?
字节面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?
78 0
|
4月前
|
存储 算法 Java
ThreadLocal 源码浅析
【7月更文挑战第5天】`ThreadLocal` 是Java中用于创建线程局部变量的工具类,确保每个线程拥有独立的变量副本。源码中,每个`ThreadLocal`实例都有一个唯一的哈希码用于映射到`ThreadLocalMap`,这个内部静态类使用弱引用存储键(`ThreadLocal`实例)以防止内存泄漏。`ThreadLocalMap`使用 Entry 数组,Entry 是一个扩展了 WeakReference 的类,持有线程变量值。`ThreadLocal`的`get()`和`set()`方法通过哈希计算定位并访问或设置线程局部变量。
|
5月前
|
存储 Java 数据库连接
来探讨一下最近面试问的ThreadLocal问题
来探讨一下最近面试问的ThreadLocal问题
|
安全 Java 数据库连接
深入浅出ThreadLocal
ThreadLocal相信大家都有用过的,一般用作存取一些全局的信息。比如用户信息,流程信息,甚至在Spring框架里面通过事务注解Transactional去获取数据库连接的实现上,也有它的一份功劳。
147 0
|
存储 前端开发 Java
ThreadLocal学习笔记(一)
ThreadLocal学习笔记(一)
ThreadLocal学习笔记(一)
|
存储 安全 Java
|
安全 Java
ThreadLocal相关知识点
ThreadLocal相关知识点
109 0
ThreadLocal相关知识点
|
算法 安全 Java
threadlocal再温习
早时总结过《ThreadLocal解析》、《FastThreadLocal解析》 最近看一些资料的时候,又重重发现了这类,不希望再温下,许多知识点,之前已经总结了,这篇文章主要有两个问题: 1、弱引用的意义 2、如何防键冲突
243 0
threadlocal再温习
|
安全 Java 关系型数据库
大神们是怎么使用ThreadLocal的?
这篇文章是关于ThreadLocal的第三篇文章。本文将挑选一些主流的Java开源框架,从源码上分析,大神们是如何使用ThreadLocal的,学习他们的设计思想。
189 0
【学习笔记】深入理解ThreadLocal(2)
【学习笔记】深入理解ThreadLocal
130 0
【学习笔记】深入理解ThreadLocal(2)