InheritableThreadLocal线程复用

引自:http://www.cnblogs.com/sweetchildomine/p/6575666.html

虽然使用AOP可以获取方法签名,但是如果要获取方法中计算得出的数据,那么就得使用ThreadLocal,如果还涉及父线程,那么可以选择InheritableThreadLocal.

注意:理解一些原理能够减少很多不可控问题,最简单的使用方式就是不要交给线程池处理.为了提高一点性能,而导致数据错误得不偿失.

2018年4月12日 12:44:41更新 关于InheritableThreadLocal 配合线程池的问题解决方案 -> TransmittableThreadLocal 解决 线程池线程复用 无法复制 InheritableThreadLocal 的问题.

首先看看ThreadLoacl如何做到共享变量实现为线程私有变量

Thread源码里面,有一个ThreadLoaclMap

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLoacl set方法源码

   public void set(T value) {    //获取当前线程
        Thread t = Thread.currentThread();    //获取当前线程ThreadLoaclMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

ThreadLoacl getMap方法源码

   ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

测试TreadLocal线程私有

public class A {
    static final ThreadLocal<String> threadParam = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        //死循环,测多几次看结果
        while (true) {
            //线程1
            new Thread(() -> {
                //设置参数
                threadParam.set("abc");
                //输出参数
                System.out.println("t1:" + threadParam.get());
                //看起来像是多余操作
//                threadParam.remove();
            }).start();
            TimeUnit.SECONDS.sleep(1);
            new Thread(() -> {
                //线程二,测试是否能获取abc
                System.out.println("t2:" + threadParam.get());
            }).start();
        }
    }
}

测试结果:

线程1永远输出abc

线程2永远输出null

看起来很美好.但是也有需要注意的地方

如果使用线程池,以下把线程交给线程池处理

/**
 *
 * @author ZhenWeiLai
 *
 */
public class B {
    static final ThreadLocal<String> threadParam = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        //固定池内只有存活3个线程
        ExecutorService execService = Executors.newFixedThreadPool(3);
        //死循环几次才能看出效果
        while (true) {
            Thread t = new Thread(()->{
                    threadParam.set("abc");
                    System.out.println("t1:" + threadParam.get());
                    //如果不调用remove,将引发问题
//                    threadParam.remove();
            });
            execService.execute(t);
            TimeUnit.SECONDS.sleep(1);
            Thread t2 = new Thread(()-> {
                    System.out.println("t2:" + threadParam.get());
            });
            execService.execute(t2);
        }
    }
}

测试结果:

t1:abc
t1:abc
t2:null
t2:abc  //因复用线程而导致问题
t1:abc

原因:线程池把线程提交到队列,当被调用的时候如果存在空闲线程就直接复用线程,仅仅是调用了用户提交的run方法.

所以当ThreadLocal参数使用完,记得调用remove方法

除了ThreadLocal 还有 InheritableThreadLocal,子线程可以共享父线程的InheritableThreadLocal

InheritableThreadLocal实现的关键源码

 //初始化一个线程时,获取当前线程,作为父线程
 Thread parent = currentThread();
//如果父线程inheritableThreadLocals 不为空时,子线程复制一份inheritableThreadLocals
 if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

测试代码

/**
 *
 * @author ZhenWeiLai
 *
 */
public class A {
    static final InheritableThreadLocal<String> threadParam = new InheritableThreadLocal<>();
    public static void main(String[] args) throws InterruptedException {
        //死循环,测多几次看结果
        while (true) {
            //线程1,测试是否能获取父线程参数
            new Thread(() -> {
                //设置参数
                threadParam.set("abc");
                //输出参数
                System.out.println("t1:" + threadParam.get());

                //线程2,测试是否能获取线程1参数
                new Thread(() -> {
                    System.out.println("t2:" + threadParam.get());
                    //为了测试线程三能否获得,这里先不删除
//                    threadParam.remove();
                }).start();
            }).start();

            TimeUnit.SECONDS.sleep(1);

            //线程3,测试是否能获取线程1参数
            new Thread(() -> {
                System.out.println("t3:" + threadParam.get());
            }).start();
        }
    }
}

输出结果:自线程可以获取参数,非自线程不能获取.

t1:abc
t2:abc
t1:abc
t3:null
t2:abc
t3:null
t1:abc
t2:abc
t3:null
t1:abc
t2:abc

再一次看似很美好,以下写一个复杂点的,交给线程池执行

package thread.base.threadloacl;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 *
 * @author ZhenWeiLai
 *
 */
public class B {
    static final InheritableThreadLocal<String> threadParam = new InheritableThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        //固定池内只有存活3个线程
        ExecutorService execService = Executors.newFixedThreadPool(3);
        //死循环几次才能看出效果
        while (true) {
            //线程1,里面有两个子线程
            Thread t = new Thread(()->{
                    threadParam.set("abc");
                    System.out.println("t1:" + threadParam.get());
                    Thread t2 = new Thread(()->{
                        System.out.println("t2:" + threadParam.get());
//                        threadParam.remove();
                    });
                    execService.execute(t2);

                    Thread t3 = new Thread(()->{
                        System.out.println("t3:" + threadParam.get());
//                        threadParam.remove();
                });
                    execService.execute(t3);

            });
            execService.execute(t);
            TimeUnit.SECONDS.sleep(1);
            //线程4,线程1同级
            Thread t4 = new Thread(()-> {
                threadParam.set("CBA");
                    System.out.println("t4:" + threadParam.get());
            });
            execService.execute(t4);
        }
    }
}

输出结果:

t1:abc
t2:abc
t3:abc
t4:CBA
t1:abc
t2:abc
t3:abc
t4:CBA
t1:abc
t2:abc
t3:CBA //因复用线程而导致问题
t4:CBA

Runnable只是线程方法,Thread才是线程,需要给Runnable加上一个线程的壳,调用start才会使用线程执行.

这里线程池只存活3个线程,那么在线程池复用线程(壳)的时候,壳的属性只有在创建的时候才会被重新设置值(如果有操作的话,例如:InheritableThreadLocal,ThreadLocal).

这些壳被创建好以后提交给了线程池,但是线程方法并没有马上执行,然后被其他壳修改了属性.当这个线程方法开始执行的时候,已经不是自己创建的壳了

这里线程3,因为由于线程切换使用了被线程4修改以后的壳的属性.

加大线程池,以满足每个线程方法独立使用一个线程只能保证第一次运行正确,因为没有涉及Thread重用的问题.但是如果涉及重用Thread(壳)的时候,没有办法可以保证.

原文地址:https://www.cnblogs.com/x-jingxin/p/10637255.html

时间: 2024-10-13 04:42:23

InheritableThreadLocal线程复用的相关文章

线程复用:线程池

一.核心线程池内部实现 为了能够更好地控制多线程,JDK提供了一套Executor框架,帮助开发人员有效地进行线程控制,其本质就是一个线程池.它的核心成员如图 以上成员均在java.util.concurrent包中,是JDK并发包的核心类.其中ThreadPoolExecutor表示一个线程池.Executors类则扮演着线程池工厂的角色,通过Executors可以取得一个拥有特定功能的线程池. Executor框架提供了各种类型的线程池,主要有以下工厂方法: public static Ex

线程池 -实现线程复用

线程池:实现线程的复用,无须反复创建线程而消耗过多的资源,提高响应速度 可用集合来实现 容器->集合(ArrayList,HashSet,LinkedList,HashMap) 原理: 当程序第一次启动时,创建多个线程,使用时采用Thread T=list.remove(),即可调用线程 同理也可用Thread T=linded.removeFirst(); 使用完后记得还回线程池,采用list.add()或linked.addLast(t); 在JDK1.5后,不用自己创建线程池了,已经内置了

3.2 线程复用:线程池

参数说明:public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defau

Java线程变量问题

关于Java线程问题,在博客上看到一篇文章挺好的: https://blog.csdn.net/w172087242/article/details/83375022#23_ThreadLocal_175 自己动手实验了一下. 1.maven设置 <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncod

Android 多线程下载,断点续传,线程池

你可以在这里看到这个demo的源码: https://github.com/onlynight/MultiThreadDownloader 效果图 这张效果图是同时开启三个下载任务,限制下载线程数量的效果图. 多线程下载原理 多线程下载的原理就是将下载任务分割成一个个小片段再将每个小片段分配给各个线程进行下载. 例如一个文件大小为100M,我们决定使用4个线程下载,那么每个线程下载的大小即为25M,每个线程的起始以及结束位置依次如下: 0: 0-25M 1: 25-50M 2: 50-75M 3

Python线程指南

1. 线程基础 1.1. 线程状态 线程有5种状态,状态转换的过程如下图所示: 1.2. 线程同步(锁) 多线程的优势在于可以同时运行多个任务(至少感觉起来是这样).但是当线程需要共享数据时,可能存在数据不同步的问题.考虑这样一种情况:一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印.那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成

Python线程指南(转)

1. 线程基础 1.1. 线程状态 线程有5种状态,状态转换的过程如下图所示: 1.2. 线程同步(锁) 多线程的优势在于可以同时运行多个任务(至少感觉起来是这样).但是当线程需要共享数据时,可能存在数据不同步的问题.考虑这样一种情况:一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印.那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成

java线程池相关知识点总结

Android中常见到的很多通用组件一般都离不开"池"的概念,如各种图片加载库,网络请求库,即使Android的消息传递机制中的Meaasge当使用Meaasge.obtain()就是使用的Meaasge池中的对象,因此这个概念很重要.本文将介绍的线程池技术同样符合这一思想. 线程池的优点:重用线程池中的线程,减少因对象创建,销毁所带来的性能开销;能有效的控制线程的最大并发数,提高系统资源利用率,同时避免过多的资源竞争,避免堵塞;能够多线程进行简单的管理,使线程的使用简单.高效. 线程

使用RCU技术实现读写线程无锁

在一个系统中有一个写线程和若干个读线程,读写线程通过一个指针共用了一个数据结构,写线程改写这个结构,读线程读取该结构.在写线程改写这个数据结构的过程中,加锁情况下读线程由于等待锁耗时会增加. 可以利用RCU (Read Copy Update What is rcu)的思想来去除这个锁.本文提到的主要实现代码:gist RCU RCU可以说是一种替代读写锁的方法.其基于一个事实:当写线程在改变一个指针时,读线程获取这个指针,要么获取到老的值,要么获取到新的值.RCU的基本思想其实很简单,参考Wh