ThreadLocal详解

一、ThreadLocal

1、说明

ThreadLocal 是一个线程局部变量。其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。

2、使用方法

ThreadLocal<String> nameThreadLocal = new ThreadLocal<>();
nameThreadLocal.set(name);
nameThreadLocal.get()

3、Request 使用案例

springMvc中单例controller中定义成员变量 request ,使用@autowired注入时,request就是使用ThreadLocal实现线程安全的。

  1. 在controller中注入的request是jdk动态代理对象,ObjectFactoryDelegatingInvocationHandler的实例.当我们调用成员域request的方法的时候其实是调用了objectFactory的getObject()对象的相关方法.这里的objectFactory是RequestObjectFactory.
  2. RequestObjectFactory的getObject其实是从RequestContextHolder的threadlocal中去取值的.
  3. 请求刚进入springmvc的dispatcherServlet的时候会把request相关对象设置到RequestContextHolder的threadlocal中去.

参考:在SpringMVC Controller中注入Request成员域 - abcwt112 - 博客园

4、参考

ThreadLocal详解

https://www.jianshu.com/p/3bb70ae81828

二、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++;
                    }
                }
            }
        }

3、参考

InheritableThreadLocal详解
https://www.jianshu.com/p/94ba4a918ff5

三、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

在这里插入图片描述

4、参考

TransmittableThreadLocal(TTL)实现线程变量传递的原理分析_未完成交响曲-KyleWang的博客-CSDN博客_transmittablethreadlocal原理

TransmittableThreadLocal使用JavaAgent动态代理机制分析_Lidisam的博客-CSDN博客

分布式链路跟踪技术(五):跨线程传输和上下文传播 - 百度文库 (baidu.com)

四、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;
	}

4、参考

TaskDecorator解决父子线程间传递上下文数据
https://blog.csdn.net/lmx1989219/article/details/110522077

五、ThreadLocal内存泄漏

https://blog.csdn.net/qq_25775675/article/details/105731434

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值