引自: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(壳)的时候,没有办法可以保证.

InheritableThreadLocal线程复用的更多相关文章

  1. Java并发程序设计(五)JDK并发包之线程复用:线程池

    线程复用:线程池 一.为什么需要线程池 为了避免系统频繁地创建和销毁线程,使用线程池让线程进行复用.(即创建线程变成了从线程池中获取空闲线程,销毁线程变成了把线程放回线程池中.) 二.JDK对线程池的 ...

  2. Java 线程池中的线程复用是如何实现的?

    前几天,技术群里有个群友问了一个关于线程池的问题,内容如图所示: 关于线程池相关知识可以先看下这篇:为什么阿里巴巴Java开发手册中强制要求线程池不允许使用Executors创建? 那么就来和大家探讨 ...

  3. TransmittableThreadLocal 解决 线程池线程复用 无法复制 InheritableThreadLocal 的问题.

    ThreadLoacl,InheritableThreadLocal,原理,以及配合线程池使用的一些坑 TransmittableThreadLocal 原理 之前为了能让InheritableThr ...

  4. ThreadLoacl,InheritableThreadLocal,原理,以及配合线程池使用的一些坑

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

  5. InheritableThreadLocal 在线程池中进行父子线程间消息传递出现消息丢失的解析

    在日常研发过程中,我们经常面临着需要在线程内,线程间进行消息传递,比如在修改一些开源组件源码的过程中,需要将外部参数透传到内部,如果进行方法参数重载,则涉及到的改动量过大,这样,我们可以依赖Threa ...

  6. java 线程池(线程的复用)

    一. 线程池简介 1. 线程池的概念: 线程池就是首先创建一些线程,它们的集合称为线程池.使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动 ...

  7. 并发之线程封闭与ThreadLocal解析

    并发之线程封闭与ThreadLocal解析 什么是线程封闭 实现一个好的并发并非易事,最好的并发代码就是尽量避免并发.而避免并发的最好办法就是线程封闭,那什么是线程封闭呢? 线程封闭(thread c ...

  8. 详解Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失

    在Spring Cloud中我们用Hystrix来实现断路器,Zuul中默认是用信号量(Hystrix默认是线程)来进行隔离的,我们可以通过配置使用线程方式隔离. 在使用线程隔离的时候,有个问题是必须 ...

  9. ThreadLocal的坑--ThreadLocal跨线程传递问题

    1.父子线程间的传递问题 ThreadLocal的子类InheritableThreadLocal其实已经帮我们处理好了,通过这个组件可以实现父子线程之间的数据传递,在子线程中能够父线程中的Threa ...

随机推荐

  1. es6新语法的使用

    1.声明变量: let 声明变量 作用域代码块作用域{} 尽在模块 先使用后声明 会报错 { let a= 12; alert(a) } let 不允许重复声明同一个变量 const 声明是一个常量, ...

  2. 关于 “VMware Workstation 不可恢复错误- (vcpu-0)”

    重装系统后第一次在 VMware Workstation 上创建虚拟机,结果出现了 VMware Workstation 不可恢复错误: (vcpu-0) 错误. 于是我们遵循它给出的提示,查看一下日 ...

  3. Web安全入门笔记-XSS

    windows 10 360浏览器 0x00.概述 1.什么是 XSS Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击.攻击者通过在目标网站上注入恶意脚本,使 ...

  4. Java NIO(四) Scatter/Gather

    Java NIO开始支持scatter/gather,scatter/gather用于描述从Channel(译者注:Channel在中文经常翻译为通道)中读取或者写入到Channel的操作.分散(sc ...

  5. log4j2简单使用

    http://www.cnblogs.com/lzb1096101803/p/5796849.html 如果是基于的slf4j,看这篇文章去配置 http://blog.csdn.net/neosmi ...

  6. android Handler、Thread和Runnable

    android里面的创建的Handler对象并不是新建一个新的线程,而是在主线程执行,主线程的消息队列中循环. java中实现一个线程有两种方法,一种是继承Thread类,一种是实现Runnable接 ...

  7. MongoDB数据库 备份 还原

    MongoDB数据库 1.备份用        mongodump 2.还原用        mongorestore 1.备份 @echo offecho 正在备份MongoDB数据库SET mon ...

  8. 实现两个N×N矩阵的乘法,矩阵由一维数组表示

    此题的关键在于找到矩阵乘法的不变式! 例如: 矩阵a × 矩阵b = 矩阵ab 1 2 5 6 × 3 4 7 8 显然 ab[0] = a[0] * b[0] + a[1] * b[2] ab[1] ...

  9. 思维导图与Spring Batch

    最近在学画图,又在复习Spring Batch.不解释,直接上图. 第三章,用XMind画的. 第五章,用iMindMap画的.

  10. asp.net反射的运用

    反射的用途:    (1)使用Assembly定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例.     (2)使用Module了解包含模块的程序集以及模块中的 ...