Java中的ThreadLocal详解

 更新时间:2023年09月26日 10:06:35   作者:菜鸟小窝  
这篇文章主要介绍了Java中的ThreadLocal详解,ThreadLocal 是一个线程局部变量,其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,需要的朋友可以参考下

一、ThreadLocal

1、说明

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

2、使用方法

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

3、Request 使用案例

  1. 在controller中注入的request是jdk动态代理对象,ObjectFactoryDelegatingInvocationHandler的实例.当我们调用成员域request的方法的时候其实是调用了objectFactory的getObject()对象的相关方法.这里的objectFactory是RequestObjectFactory
  2. RequestObjectFactory的getObject其实是从RequestContextHolder的threadlocal中去取值的
  3. 请求刚进入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详解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot默认使用HikariDataSource数据源方式

    SpringBoot默认使用HikariDataSource数据源方式

    这篇文章主要介绍了SpringBoot默认使用HikariDataSource数据源方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • JAVA错误:'无效目标发行版 17'的解决方案

    JAVA错误:'无效目标发行版 17'的解决方案

    这篇文章主要给大家介绍了关于JAVA错误:'无效目标发行版 17'的解决方案,文中通过图文介绍的非常详细,对大家学习或使用java具有一的的参考学习价值,需要的朋友可以参考下
    2022-09-09
  • springboot @Validated的概念及示例实战

    springboot @Validated的概念及示例实战

    这篇文章主要介绍了springboot @Validated的概念以及实战,使用 @Validated 注解,Spring Boot 应用可以有效地实现输入验证,提高数据的准确性和应用的安全性,本文结合实例给大家讲解的非常详细,需要的朋友可以参考下
    2024-04-04
  • java抓取12306信息实现火车余票查询示例

    java抓取12306信息实现火车余票查询示例

    这篇文章主要介绍了java抓取12306信息实现火车余票查询示例,需要的朋友可以参考下
    2014-04-04
  • 图解Java ReentrantLock公平锁和非公平锁的实现

    图解Java ReentrantLock公平锁和非公平锁的实现

    ReentrantLock是Java并发中十分常用的一个类,具备类似synchronized锁的作用。但是相比synchronized, 它具备更强的能力,同时支持公平锁和非公平锁。本文就来聊聊ReentrantLock公平锁和非公平锁的实现,需要的可以参考一下
    2022-10-10
  • Java多线程继承Thread类详解

    Java多线程继承Thread类详解

    Java多线程的两种实现方式:继承Thread类 & 实现Runable接口,今天我们来学习下继承Thread类,希望大家能够喜欢
    2016-06-06
  • Java中Word与PDF转换为图片的方法详解

    Java中Word与PDF转换为图片的方法详解

    这篇文章主要为大家详细介绍了如何使用Java实现将Word与PDF转换为图片,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-10-10
  • MyBatisPlus中事务处理的实现

    MyBatisPlus中事务处理的实现

    本文主要介绍了MyBatisPlus中事务处理的实现,包括事务的开启、提交、回滚等操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-07-07
  • 详解Spring @Autowired 注入小技巧

    详解Spring @Autowired 注入小技巧

    这篇文章主要介绍了详解Spring @Autowired 注入小技巧,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-07-07
  • SpringMVC整合SpringSession 实现sessiong

    SpringMVC整合SpringSession 实现sessiong

    这篇文章主要介绍了SpringMVC整合SpringSession 实现session的实例代码,本文通过实例相结合的形式给大家介绍的非常详细,需要的朋友参考下吧
    2018-04-04

最新评论