ThreadLoacl,InheritableThreadLocal,原理,以及配合线程池使用的一些坑
虽然使用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(壳)的时候,没有办法可以保证.
本篇问题还未结束...使用线程池如何Remove ThreadLocal参数...使用线程池其他提交方法结果也不一样等等,有空再补充.
ThreadLoacl,InheritableThreadLocal,原理,以及配合线程池使用的一些坑的更多相关文章
- juc线程池原理(四): 线程池状态介绍
<Thread之一:线程生命周期及五种状态> <juc线程池原理(四): 线程池状态介绍> 线程有5种状态:新建状态,就绪状态,运行状态,阻塞状态,死亡状态.线程池也有5种状态 ...
- 从使用到原理学习Java线程池
线程池的技术背景 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收. 所 ...
- 【转载】从使用到原理学习Java线程池
线程池的技术背景 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收. 所 ...
- Java线程池实现原理之自定义线程池(一)
1.队列的概念 谈到多线程先讲下队列的概念,之后的多线程学习会用到此类知识. 队列分为:阻塞式队列(有界).非阻塞式队列(无界),遵循着先进先出.后进后出的原则.阻塞队列与非阻塞队列区别: 1.非阻塞 ...
- 线程池的原理以及实现线程池的类ExecutorService中方法的使用
1.线程池:线程池就是就像一个容器,而这个容器就是用来存放线程的,且有固定的容量. 如果没有线程池,当需要一个线程来执行任务时就需要创建一个线程,我们设创建线程的时间为t1,执行线程的时间为t2,销毁 ...
- java使用默认线程池踩过的坑(三)
云智慧(北京)科技有限公司 陈鑫 重新启动线程池 TaskManager public class TaskManager implements Runnable { -.. public TaskM ...
- java使用默认线程池踩过的坑(二)
云智慧(北京)科技有限公司 陈鑫 是的.一个线程不可以启动两次.那么它是怎么推断的呢? public synchronized void start() { /** * A zero status v ...
- TransmittableThreadLocal 解决 线程池线程复用 无法复制 InheritableThreadLocal 的问题.
ThreadLoacl,InheritableThreadLocal,原理,以及配合线程池使用的一些坑 TransmittableThreadLocal 原理 之前为了能让InheritableThr ...
- Java线程池实现原理与技术(ThreadPoolExecutor、Executors)
本文将通过实现一个简易的线程池理解线程池的原理,以及介绍JDK中自带的线程池ThreadPoolExecutor和Executor框架. 1.无限制线程的缺陷 多线程的软件设计方法确实可以最大限度地发 ...
随机推荐
- [Java 教程 01] Hello,Java!
前言 从事编程已经有一段时间了,突然发现,Java作为我的第一编程语言,自己似乎对她并有一个系统的思想.当下Java依旧保持着超高的热度,新特性也不断出现,从当初学习的java6版本到最近刚出的jav ...
- ClearCase创建视图与基本命令
1.创建和设置view cleartool mkview -tag King_dev /home/King/King_dev.vws cleartool setview King_dev 2.删除V ...
- Elasticsearch 全教程--入门
1.1 初识 Elasticsearch 是一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎,可以说 Lucene 是当今最先进,最高效的全功能开源搜索引擎框架. 但是 L ...
- java 多维数组遍历
java 多维数组遍历的顺序的性能问题 ps:下图为java多维数组内存分布原理.出自:http://math.hws.edu/javanotes/c7/two-dimensional-array.p ...
- java for循环增强(foreach)
for循环增强,在此之前还不知道foreach有这样的功能,先鄙视一下自己,留给自己看: 功能: ***若List用foreach : [ for(Student stu : list) ]这种形 ...
- java基础(六) switch语句的深入解析
引言 switch 语句是非常的基础的知识,掌握起来也不难掌握,语法比较简单.但大部分人基本是知其然,不知其所以然.譬如 早期JDK只允许switch的表达式的值 int及int类型以下的基本类型 ...
- Chrome浏览器调试Android的Webview
chrome://inspect Android:4.4+ Chrome 30+ 首次使用需要FQ
- 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端
接上文 多线程编程学习笔记——使用异步IO 二. 编写一个异步的HTTP服务器和客户端 本节展示了如何编写一个简单的异步HTTP服务器. 1.程序代码如下. using System; using ...
- 隐藏index.php
以 Apache 为例,需要在入口文件的同级添加 .htaccess 文件(官方默认自带了该文件),内容如下:<IfModule mod_rewrite.c>Options +Follow ...
- java.util.logging.Logger基础
1. 定义 java.util.logging.Logger是Java自带的日志类,可以记录程序运行中所产生的日志.通过查看所产生的日志文件,可以分析程序的运行状况,出现异常时,分析及定位异常. 2. ...