ThreadLocal 遇上线程池的问题及解决办法
ThreadLocal 称为线程本地存储,一般作为静态域使用,它为每一个使用它的线程提供一个其值(value)的副本。通常对数据库连接(Connection)和事务(Transaction)使用线程本地存储。
可以简单地将 ThreadLocal<T> 理解成一个容器,它将 value 对象存储在 Map<Thread, T> 域中,即使用当前线程为 key 的一个 Map,ThreadLocal 的 get() 方法从 Map 里取与当前线程相关联的 value 对象。ThreadLocal 的真正实现并不是这样的,但是可以简单地这样理解。
线程池中的线程在任务执行完成后会被复用,所以在线程执行完成时,要对 ThreadLocal 进行清理(清除掉与本线程相关联的 value 对象)。不然,被复用的线程去执行新的任务时会使用被上一个线程操作过的 value 对象,从而产生不符合预期的结果。
下面举一个简单的例子来说明:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class ThreadLocalVariableHolder {
private static ThreadLocal<Integer> variableHolder = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
}; public static int getValue() {
return variableHolder.get();
} public static void remove() {
variableHolder.remove();
} public static void increment() {
variableHolder.set(variableHolder.get() + 1);
} public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executor.execute(() -> {
int before = getValue();
increment();
int after = getValue();
System.out.println("before: " + before + ", after: " + after);
});
} executor.shutdown();
}
}
执行结果如下(如果你的执行结果 before 都是 0,after 都是 1 的话,就增加线程池执行的线程个数):
before: 0, after: 1
before: 0, after: 1
before: 0, after: 1
before: 1, after: 2
before: 1, after: 2
既然是为每个线程都提供一个副本,为什么会出现 before 不为 0 的情况呢?
下面追踪每一个执行的线程,将 main 方法修改为如下:
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executor.execute(() -> {
long threadId = Thread.currentThread().getId();
int before = getValue();
increment();
int after = getValue();
System.out.println("threadId: " + threadId + ", before: " + before + ", after: " + after);
});
}
executor.shutdown();
}
执行结果如下:
threadId: 10, before: 0, after: 1
threadId: 11, before: 0, after: 1
threadId: 12, before: 0, after: 1
threadId: 12, before: 1, after: 2
threadId: 11, before: 1, after: 2
由上面的执行结果可以看出,id 为 11 和 12 的线程被复用。线程池在复用线程执行任务时使用被之前的线程操作过的 value 对象。因此,在每个线程执行完成时,应该清理 ThreadLocal。具体做法如下:
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
executor.execute(() -> {
try {
long threadId = Thread.currentThread().getId();
int before = getValue();
increment();
int after = getValue();
System.out.println("threadId: " + threadId + ", before: " + before + ", after: " + after);
} finally {
// 清理线程本地存储
remove();
}
});
}
executor.shutdown();
}
ThreadLocal 遇上线程池的问题及解决办法的更多相关文章
- threadLocal遇上线程池导致局部变量变化
这两天一直在查无线app一个诡异的问题,表象是stg的接口返回数据,和线上接口的返回数据不一致. 1.初步判断:有缓存,查看代码后发现缓存时间直邮6分钟,而且同一个接口,其他调用方的返回数据,stg和 ...
- Springboot数据库连接池报错的解决办法
Springboot数据库连接池报错的解决办法 这个异常通常在Linux服务器上会发生,原因是Linux系统会主动断开一个长时间没有通信的连接 那么我们的问题就是:数据库连接池长时间处于间歇状态,导致 ...
- mac上Navicat新建数据库3680错误解决办法
mac上Navicat新建数据库3680错误解决办法 1.在设置里关闭mysql,若不能关闭,在终端输入: sudo /usr/local/mysql/support-files/mysql.serv ...
- ThreadPoolExecutor使用和思考(上)-线程池大小设置与BlockingQueue的三种实现区别
工作中多处接触到了ThreadPoolExecutor.趁着现在还算空,学习总结一下. 前记: jdk官方文档(javadoc)是学习的最好,最权威的参考. 文章分上中下.上篇中主要介绍ThreadP ...
- hreadPoolExecutor使用和思考(上)-线程池大小设置与BlockingQueue的三种实现区别
阅读更多 工作中多处接触到了ThreadPoolExecutor.趁着现在还算空,学习总结一下. 前记: jdk官方文档(javadoc)是学习的最好,最权威的参考. 文章分上中下.上篇中主要介绍Th ...
- Java主线程等待所有子线程执行完毕再执行解决办法(转)
方法一: Thread.join()方法,亲测可行,thread.join()方法 Vector<Thread> ts = new Vector<Thread>(); for ...
- 服务器上运行程序Out of memory 解决办法
****** 服务器上跑过程序经常能遇到out of memory 这个问题,下面是我经常在实验室碰到的解决方法. 1.使用命令nvidia-smi,看到GPU显存被占满: 2.尝试使用 ps aux ...
- JAVA:当数据库重启后连接池没有自动识别的解决办法
今天发现服务器上的一个服务程序出现问题,软件抛出:Connection reset by peer: socket write error 无法正常提供服务,找了一下原因,原来是因为数据库服务器重启, ...
- OC-多线程安全隐患及一般解决办法
1.多线程的安全隐患1.1>一块资源可能被多个线程共享,也就是多个线程可能会访问同一块资源,如多个线程访问同一个对象,变量,文件等当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题1. ...
随机推荐
- HTML初识
HTML初识 web服务本质 import socket def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) so ...
- .Net中Web增加加密狗管理
由于业务中最近需要使用到加密狗,增加对Web代码的管控,所以需要进行加密狗使用的研究 首先,对于没有接触使用过加密狗的人需要有个大致的认识,加密狗分为 MasterDog, 1.下载加密狗的开发套件, ...
- 1.4WEB API 路由配置及参数传输
在没有添加路由时,webapi 的访问比较恶心,访问的时候是没有接口方法的.如果定义了同样参数的方法,它就傻傻的分不清的,为了解决这个问题,我们加上路由吧. 这是生成的api帮助文档列表,可以看到下面 ...
- Java学习图形界面+网络编程案例---------网络简易通讯
主要思想: 主类继承JPanel,在构造方法中将JFrame设成空布局:在其中适当位置添加组件:实现事件监听处理 DATE:2015-10-31 服务器端代码: /** * @author Oyc * ...
- 机器学习基石:16 Three Learning Principles
三个理论上界: 三个线性模型: 三个关键工具: 三条学习规则: 1.奥卡姆剃刀定律 先从简单模型开始, 训练后出现欠拟合, 再尝试复杂点模型. 2.采样误差 训练.验证.测试数据尽量同分布. 3.数据 ...
- 【USACO】电子游戏 有条件的背包
题目描述 翰的奶牛玩游戏成瘾!本来约翰是想把她们拖去电击治疗的,但是他发现奶牛们在玩游戏后生产 了更多的牛奶,也就支持它们了. 但是,奶牛在选择游戏平台上的分歧很大:有些奶牛想买 Xbox 360 来 ...
- ●HDU 2871 Memory Control(Splay)
●赘述题目 四种操作: ○Reset:将整个内存序列清空. ○New a:在尽量靠左的位置新建一个长度为a的内存块,并输出改内存块起始位置.(各个内存块即使相邻也不会合并..) ○Free a:将a点 ...
- HDU2222 自动机(学习中)
题目大意: 给你很多个单词,然后给你一篇文章,问给出的单词在文章中出现的次数. 解题思路: AC自动机入门题.需要注意的就是可能有重复单词 代码如下: #include<iostream> ...
- [bzoj4763]雪辉&[bzoj4812][Ynoi2017]由乃打扑克
来自FallDream的博客,未经允许,请勿转载,谢谢. cut掉部分题面. 给一个n个点的树,点有点权,有m次询问,每次询问多条链的并有多少种不同的点权以及它的mex mex就是一个集合中最小的没有 ...
- Frame buffer分析 - fbmem.c【转】
转自:http://www.cnblogs.com/armlinux/archive/2012/03/01/2396753.html 45 struct fb_info *registered_fb[ ...