文章目录

  • 1、概览
  • 2、ThreadLocal API
  • 3、存储用户数据在ConcurentHashMap中
  • 4、存储用户数据在ThreadLocal中
  • 5、小心把ThreadLocal和ExecutorService一起使用

1、概览

本文我们来看下java.lang包中的ThreadLocal,它赋予我们给每个线程存储自己数据的能力。

2、ThreadLocal API

ThreadLocal允许我们存储的数据只能被特定的线程``访问
我们现在存储一个整形并把它和一个特定的线程绑定:

ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();

接下来当我们在某个线程中想使用这个值的时候,我们只需要调用get()或set()方法,简单的说,我们可以把ThreadLocal理解成数据都存在一个map中,使用线程对象作为key。

当我们在当前线程中调用threadLocalValue的get()方法时,我们能拿到整形值1:

threadLocalValue.set(1);
Integer result = threadLocalValue.get();

我们可以使用ThreadLocal的withInitial()方法并传入一个supplier来创建一个实例:

ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);

想要删除这个值的时候我们只需要调用一下remove()方法就好了

threadLocal.remove();

在叙述怎么合适的使用ThreadLocal之前我们先来看一个不用ThreadLocal的例子,然后我们再修改例子比较一下。

3、存储用户数据在ConcurentHashMap中

有这么一个程序需要给每一个用户ID存储对应的用户上下文信息:

public class Context {private String userName;public Context(String userName) {this.userName = userName;}
}

我们给每个用户新起一个线程,创建了一个实现了Runnable接口的SharedMapWithUserContext类,run()方法中的UserRepository会查询数据库返回传入用户ID的用户上下文信息。

接下来我们把用户信息以用户ID为key存入ConcurentHashMap中:

public class SharedMapWithUserContext implements Runnable {public static Map<Integer, Context> userContextPerUserId= new ConcurrentHashMap<>();private Integer userId;private UserRepository userRepository = new UserRepository();@Overridepublic void run() {String userName = userRepository.getUserNameForUserId(userId);userContextPerUserId.put(userId, new Context(userName));}// standard constructor
}

我们来测试一下代码,给两个用户ID创建两个线程,在运行结束设置断言:userContextPerUserId的大小为2:

SharedMapWithUserContext firstUser = new SharedMapWithUserContext(1);
SharedMapWithUserContext secondUser = new SharedMapWithUserContext(2);
new Thread(firstUser).start();
new Thread(secondUser).start();assertEquals(SharedMapWithUserContext.userContextPerUserId.size(), 2);

4、存储用户数据在ThreadLocal中

我们重写一下我们的例子,这次把用户信息存在ThreadLocal中,每个线程都有自己的ThreadLocal实例。

我们在使用的时候要特别小心因为每个ThreadLocal实例都关联了一个特定的线程,在我们的例子中,我们给每个用户ID创建了一个专用的线程,并且这是我们自己创建出来的,我们可以完全控制它们。(为什么这么说后面会解释到)

run()方法拿到用户信息构造上下文对象并使用ThreadLocal的set()方法存储起来:

public class ThreadLocalWithUserContext implements Runnable {private static ThreadLocal<Context> userContext = new ThreadLocal<>();private Integer userId;private UserRepository userRepository = new UserRepository();@Overridepublic void run() {String userName = userRepository.getUserNameForUserId(userId);userContext.set(new Context(userName));System.out.println("thread context for given userId: "+ userId + " is: " + userContext.get());}// standard constructor
}

我们开启两个线程测试一下:

ThreadLocalWithUserContext firstUser = new ThreadLocalWithUserContext(1);
ThreadLocalWithUserContext secondUser = new ThreadLocalWithUserContext(2);
new Thread(firstUser).start();
new Thread(secondUser).start();

代码运行完能看到ThreadLocal在每个线程中都设置了值:

thread context for given userId: 1 is: Context{userNameSecret='18a78f8e-24d2-4abf-91d6-79eaa198123f'}
thread context for given userId: 2 is: Context{userNameSecret='e19f6a0a-253e-423e-8b2b-bca1f471ae5c'}

5、小心把ThreadLocal和ExecutorService一起使用

  • 当前例子
    如果我们使用ExecutorService并往里面提交Runnable任务,使用ThreadLocal会出现不确定的结果。因为我们不能确定操作每个用户ID的Runnable任务每次都是被相同的线程操作,因此ThreadLocal会被不用的用户ID公用。

  • 不同的业务使用场景不同,也不能一棒子打死,比如下面这个例子
    最近在使用Spring的状态机的时候,处理一个动作发起的逻辑需要调用状态机实例的sendEvent方法发送消息,状态机返回当前事件是否处理成功,但不方便使用的是这个sendEvent返回的值是个boolean类型,里面如果有错误,我拿不到错误信息,抛异常也会被内部捕获到返回false:

/*** Send an event {@code E} wrapped with a {@link Message} to the region.** @param event the wrapped event to send* @return true if event was accepted*/
boolean sendEvent(Message<E> event);

这个例子和上述不同的地方在于**我使用状态机在每个Runnable任务执行中只需要拿到本次运行的信息,而不需要把当前信息和后面提交的任务共享使用。**所以我调用get()拿到我在另一处逻辑里设置的错误信息时,我立即调用remove()表示本次ThreadLocal结束了。

状态机判断逻辑:

if (checkInvoice == null) {@SuppressWarnings("unchecked")ThreadLocal<String> requestError = (ThreadLocal<String>) messageHeaders.get(THREAD_LOCAL_NAME);Objects.requireNonNull(requestError).set("发票不能为空");return false;
}

状态机外部处理逻辑:

// 返回的结果是Guard的返回结果
boolean isHandleSuccess = stateMachine.sendEvent(message);
if (isHandleSuccess) {S currentState = stateMachine.getState().getId();if (previousState != currentState) {stateMachinePersister.persist(stateMachine, uniqueKey);} else {log.info("状态没有变更,不需要持久化状态。previousState={},currentState={}",previousState, currentState);}if (previousState.checkNextState(currentState)) {return buildSuccessResult(object, event.desc() + SUCCESS_TEXT);} else {String error = requestError.get();log.info("ThreadLocal value:{}", error);requestError.remove();return buildFailureResult(object, error);}
}

人人都能学会系列之ThreadLocal相关推荐

  1. 人人都能学会的python编程教程(基础篇)完整版

    人人都能学会的python编程教程1:第一行代码 人人都能学会的python编程教程2:数据类型和变量 人人都能学会的python编程教程3:字符串和编码 人人都能学会的python编程教程4:关系运 ...

  2. 学计算机的会重装系统吗,人人都能学会重装系统,你还在抱着电脑去实体店重装系统吗?...

    人人都能学会重装系统,你还在抱着电脑去实体店重装系统吗? 简单快速重装系统 在这个互联网发达的时代,手机和电脑都是大多数人常用的工具. 在使用电脑的过程中,相信很多网友们都遇到过这种情况,电脑越用越卡 ...

  3. 带瀑布流的电钢琴_让人人都能学会弹钢琴——零基础入门的智能电钢琴 POP Piano...

    原标题:让人人都能学会弹钢琴--零基础入门的智能电钢琴 POP Piano 前言 其实你一直希望成为一个会弹琴的人,只是一直没有机会开始. 如果是20世纪是网络纪元,那么21世纪就是AI纪元.当人类创 ...

  4. 文言文编程可以编译成PHP吗,人人都可以学会编译原理-开篇

    编译技术在我们日常的工作中可以说无处不在,React JSX语法的解析,Typescript转化为Javascript,XML.JSON的解析,Spring字节码生成技术,PHP的模板引擎,还有最近很 ...

  5. 人人都能学会的python编程教程1:第一行代码

    前言 众所周知,现在IT行业很火,行业薪酬也很高,国家在2017年也发布了人工智能教育的推广计划,人人会编程的时代将要到来.不会编程.不懂编程可能有些跟不上时代的节奏,普通人不懂技术也许会觉得这个很难 ...

  6. python是人都能学会_人人都能学会的python编程教程15:高级特性2

    生成器 如果你想要一百万个数,而这些数里只有一百个数是你经常要用的,剩下的都几乎不怎么会用到,那么如果直接把这一百万个数全部放在list中是不明智的因为这会浪费较多存储空间,生成器就是为了解决这个问题 ...

  7. 中点坐标公式 矩形_压轴题必备|中考数学“动点坐标”问题,这个万能解法人人都能学会!...

    中考数学压轴题考什么? "存在性问题"一定榜上有名.而再深入研究,你就会发现:这些试题中,有近四分之一都是在考查"平行四边形的存在性问题(包括矩形和菱形)". ...

  8. 怎么格式化电脑_U盘格式化后数据能恢复吗?人人都能学会的恢复方法!

    获取专业数据恢复软件: 专注硬盘U盘误删文件数据恢复软件免费下载​dl-next.aunbox.cn 数据恢复官网: 嗨格式数据恢复大师官网 - 专业U盘/电脑/硬盘数据恢复软件_免费下载​huifu ...

  9. 人人都能学会的英语5:读写

    前言 读写篇相对独立,与听说篇没有依赖关系,但需要确保前面的基础已打好. 阅读能力升级之旅 我先给出自己经历过的全英阅读能力的变化过程,仅供参考: 看到英文网站,第一反应是点击切换中文版 一些技术资料 ...

最新文章

  1. node js npm 和 cnpm的使用
  2. Python爬虫==【openurl】
  3. 【10.4】线程同步--Lock、RLock
  4. 数据库(Mysql)背后的数据结构-学习
  5. Maven:Generating Project in Batch mode 卡住问题
  6. 文献记录(part83)--Building outlier detection ensembles by selective parameterization of ...
  7. python fsolve说明_Python fsolve()抱怨形状.为什么?
  8. fzu 2109 Mountain Number 数位DP
  9. HTML - 文本及其格式化
  10. 华为鸿蒙智慧屏_华为智慧屏X65将于4月8日发布搭载了鸿蒙OS操作系统
  11. eclipse中如何搜索带\的字串
  12. 雷电模拟器android4.2,雷电安卓模拟器-雷电模拟器下载 v4.0.55.0官方版--pc6下载站...
  13. 因为mac不支持移动硬盘的NTFS格式,mac电脑无法写入移动硬盘的终极解决办法(方便好用)
  14. 怎样设置电脑桌面共享计算机,局域网共享设置,小编教你电脑怎么设置局域网共享...
  15. pywifi安装的一个坑
  16. JAVA多线程模拟火车站售票大厅
  17. Semantic UI 之 标签 label
  18. IOS双重认证增加手机号
  19. Keil_uvision 基本使用教程
  20. 流程引擎规则引擎_规则引擎的优势

热门文章

  1. 一元运算符 + 表示正号 - 表示负号
  2. 怎么把PDF转换成PPT
  3. 为什么苹果要出7寸的iPad mini?
  4. 一维数组和二维数组的定义及用法
  5. 尚有缺者,方为完美。小人求全,智者求阙——《致温弟沅弟》曾国藩
  6. 用python可以做什么有趣的事情_用python做一些有趣的事(一)——根据照片制作版画...
  7. 深圳垃圾分类告诉你这些都是什么垃圾 不仅要会分还要分的准确才行
  8. jpg格式图片怎么缩小?如何压缩jpg图片?
  9. 如何解读测试结果出现负数
  10. 代码库_单精度浮点减法器