对于多任务,Java标准库提供的线程池可以方便地执行这些任务,同时复用线程。Web应用程序就是典型的多任务应用,每个用户请求页面时,我们都会创建一个任务,类似:

public void process(User user) {checkPermission();doWork();saveStatus();sendResponse();
}

然后,通过线程池去执行这些任务。

观察process()方法,它内部需要调用若干其他方法,同时,我们遇到一个问题:如何在一个线程内传递状态?

process()方法需要传递的状态就是User实例。有的童鞋会想,简单地传入User就可以了:

public void process(User user) {checkPermission(user);doWork(user);saveStatus(user);sendResponse(user);
}

但是往往一个方法又会调用其他很多方法,这样会导致User传递到所有地方:

void doWork(User user) {queryStatus(user);checkStatus();setNewStatus(user);log();
}

这种在一个线程中,横跨若干方法调用,需要传递的对象,我们通常称之为上下文(Context),它是一种状态,可以是用户身份、任务信息等。

给每个方法增加一个context参数非常麻烦,而且有些时候,如果调用链有无法修改源码的第三方库,User对象就传不进去了。

Java标准库提供了一个特殊的ThreadLocal,它可以在一个线程中传递同一个对象。

ThreadLocal实例通常总是以静态字段初始化如下:

static ThreadLocal<User> threadLocalUser = new ThreadLocal<>();

它的典型使用方式如下:

void processUser(user) {try {threadLocalUser.set(user);step1();step2();} finally {threadLocalUser.remove();}
}

通过设置一个User实例关联到ThreadLocal中,在移除之前,所有方法都可以随时获取到该User实例:

void step1() {User u = threadLocalUser.get();log();printUser();
}void log() {User u = threadLocalUser.get();println(u.name);
}void step2() {User u = threadLocalUser.get();checkUser(u.id);
}

注意到普通的方法调用一定是同一个线程执行的,所以,step1()step2()以及log()方法内,threadLocalUser.get()获取的User对象是同一个实例。

实际上,可以把ThreadLocal看成一个全局Map<Thread, Object>:每个线程获取ThreadLocal变量时,总是使用Thread自身作为key:

Object threadLocalValue = threadLocalMap.get(Thread.currentThread());

因此,ThreadLocal相当于给每个线程都开辟了一个独立的存储空间,各个线程的ThreadLocal关联的实例互不干扰。

最后,特别注意ThreadLocal一定要在finally中清除:

try {threadLocalUser.set(user);...
} finally {threadLocalUser.remove();
}

这是因为当前线程执行完相关代码后,很可能会被重新放入线程池中,如果ThreadLocal没有被清除,该线程执行其他代码时,会把上一次的状态带进去。

为了保证能释放ThreadLocal关联的实例,我们可以通过AutoCloseable接口配合try (resource) {...}结构,让编译器自动为我们关闭。例如,一个保存了当前用户名的ThreadLocal可以封装为一个UserContext对象:

public class UserContext implements AutoCloseable {static final ThreadLocal<String> ctx = new ThreadLocal<>();public UserContext(String user) {ctx.set(user);}public static String currentUser() {return ctx.get();}@Overridepublic void close() {ctx.remove();}
}

使用的时候,我们借助try (resource) {...}结构,可以这么写:

try (var ctx = new UserContext("Bob")) {// 可任意调用UserContext.currentUser():String currentUser = UserContext.currentUser();
} // 在此自动调用UserContext.close()方法释放ThreadLocal关联对象

这样就在UserContext中完全封装了ThreadLocal,外部代码在try (resource) {...}内部可以随时调用UserContext.currentUser()获取当前线程绑定的用户名。

练习

从https://gitee.com/下载练习:ThreadLocal练习 (推荐使用IDE练习插件快速下载)

小结

ThreadLocal表示线程的“局部变量”,它确保每个线程的ThreadLocal变量都是各自独立的;

ThreadLocal适合在一个线程的处理流程中保持上下文(避免了同一参数在所有方法中传递);

使用ThreadLocal要用try ... finally结构,并在finally中清除。

ThreadLocal的作用相关推荐

  1. ThreadLocal作用、原理以及问题

    ThreadLocal 1.ThreadLocal的作用 在多线程访问共享资源时会采取一定的线程同步方式(如:加锁)来解决带来的并发问题.(如图) 使用ThreadLocal对共享资源的访问也可以解决 ...

  2. ThreadLocal源码分析

    ThreadLocal的作用 Java对象是线程间共享的,但有时我们需要一些线程间隔离的对象,该对象只能由同一个线程读写,对其他线程不可见.ThreadLocal正式提供了这样的机制,详细使用方式请参 ...

  3. ThreadLocal的使用方法

    ThreadLocal的含义是Thread Local Variable,它可以声明一个字段,使得不同的线程访问这个字段时,获取的都是不同的副本,互不影响. ThreadLocal的作用和在每个Thr ...

  4. 深入浅出ThreadLocal,你会吗?

    ThreadLocal全面解析 学习目标 了解ThreadLocal的介绍 掌握ThreadLocal的运用场景 了解ThreadLocal的内部结构 了解ThreadLocal的核心方法源码 了解T ...

  5. 面试官:听说你精通并发编程,来说说你对ThreadLocal的理解

    ThreadLocal 简介 ThreadLocal 是一个解决多线程并发问题的工具类,ThreadLocal有的人可能理解为本地线程,这个并不是正确的理解.「ThreadLocal并不是一个线程,应 ...

  6. threadlocal用法_ThreadLocal源码分析

    java.lang.ThreadLocal类在平时的开发中很少用到,勾勾工作5年多一直没有用过.最近在学习spring源码时发现很多地方用到,并且这个类是面试高频题目,不明白为什么大厂喜欢考察这个类的 ...

  7. threadlocal存连接对象的目的_面试官:知道ThreadLocal嘛?谈谈你对它的理解?

    在java的多线程模块中,ThreadLocal是经常被提问到的一个知识点,提问的方式有很多种,可能是循序渐进也可能是就像我的题目那样,因此只有理解透彻了,不管怎么问,都能游刃有余. 这篇文章主要从以 ...

  8. 获取返回值作为变量_解决多线程间共享变量线程安全问题的大杀器——ThreadLocal...

    微信公众号:Zhongger 我是Zhongger,一个在互联网行业摸鱼写代码的打工人! 关注我,了解更多你不知道的[Java后端]打工技巧.职场经验等- 上一期,讲到了关于线程死锁.用户进程.用户线 ...

  9. ThreadLocal的使用及原理分析

    文章简介 ThreadLocal应该都比较熟悉,这篇文章会基于ThreadLocal的应用以及实现原理做一个全面的分析 内容导航 什么是ThreadLocal ThreadLocal的使用 分析Thr ...

  10. Java 并发编程:ThreadLocal 的使用及其源码实现

    1.ThreadLocal的使用 防止任务在共享资源上产生冲突的一种方式是根除对变量的共享,使用线程的本地存储为使用相同变量的不同线程创建不同的存储. 下面是一个 ThreadLocal 的实例.这里 ...

最新文章

  1. Treemap and Treeset java 实现
  2. 伯明翰大学计算机科学网络安全硕士,2020年伯明翰大学网络安全硕士专业硕士申请条件-学费-世界排名...
  3. 银河麒麟4安装MySQL8_2020-03-24 linux 安装mysql8.0
  4. android Arrays.fill()的使用
  5. linux 重定位arm,Arm linxu启动过程分析(一)
  6. office组件导入导出常见异常记录
  7. 使用Angular Router导航基础
  8. 以收音机为例总结分析硬件电路的三个步骤
  9. 一、OpenTCS4.12 创建一个新的通信驱动
  10. layui合并单元格(此方式适用于没有列冻结的单元格合并)
  11. Do带你解析:原生APP与web APP的区别
  12. matlab中signal pulses,MATLAB信号处理仿真-基带脉冲成形的数字滤波器
  13. PHP - MAC下PhpStorm安装调试环境xdebug
  14. 百度地图离线开发demo(热力图)
  15. 2019年最新中文TTS算法实现个合成样本
  16. java74-GUL面板
  17. 【模型部署】PaddleOCR模型openvino部署(一)
  18. 【字符集】字符集和编码知识【转】
  19. php中英尺厘米换算,将cm换算为ft (厘米换算为英尺)
  20. 小爱同学、Blinker 控制esp32自带灯熄灭---Micropython版本

热门文章

  1. MySQL游标(cursor) 定义及使用
  2. python 的对象内建方法:__XXX__(a1,a2)
  3. 代码分析系列 数3退1
  4. 微信公众平台体验之三(手机号归属)
  5. Docker客户端连接Docker Daemon的方式
  6. 禁止浏览器缓存input值
  7. POJ 3597 Polygon Division (DP)
  8. 工作流图形设计器参考资料
  9. listview去掉底部多出的边框黑色
  10. Java-web下使用RSA进行加密解密操作