使用ThreadLocal

阅读: 135212

多线程是Java实现多任务的基础,Thread对象代表一个线程,我们可以在代码中调用Thread.currentThread()获取当前线程。例如,打印日志时,可以同时打印出当前线程的名字:

// Thread
public class Main {
public static void main(String[] args) throws Exception {
log("start main...");
new Thread(() -> {
log("run task...");
}).start();
new Thread(() -> {
log("print...");
}).start();
log("end main.");
} static void log(String s) {
System.out.println(Thread.currentThread().getName() + ": " + s);
}
}

Run

main: start main...
Thread-0: run task...
Thread-1: print...
main: end main.

对于多任务,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();
} @Override
public 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://www.liaoxuefeng.com/wiki/1252599548343744/1306581251653666

使用ThreadLocal的更多相关文章

  1. ThreadLocal简单理解

    在java开源项目的代码中看到一个类里ThreadLocal的属性: private static ThreadLocal<Boolean> clientMode = new Thread ...

  2. Android线程管理之ThreadLocal理解及应用场景

    前言: 最近在学习总结Android的动画效果,当学到Android属性动画的时候大致看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣 ...

  3. Threadlocal使用Case

    Threadlocal能够为每个线程分配一份单独的副本,使的线程与线程之间能够独立的访问各自副本.Threadlocal 内部维护一个Map,key为线程的名字,value为对应操作的副本. /** ...

  4. 多线程映射工具——ThreadLocal

    ThreadLocal相当于一个Map<Thread, T>,各线程使用自己的线程对象Thread.currentThread()作为键存取数据,但ThreadLocal实际上是一个包装了 ...

  5. ThreadLocal 工作原理、部分源码分析

    1.大概去哪里看 ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性 ThreadLocal.ThreadLocalMap t ...

  6. ThreadLocal<T>的是否有设计问题

    一.吐槽 ThreadLocal<T>明显是.NET从JAVA中来的一个概念,但是这种设计是否出现了问题. 很明显,在JAVA中threadLocal直接是Thread的成员,当然随着th ...

  7. 理解ThreadLocal —— 一个map的key

    作用: 当工作于多线程中的对象使用ThreadLocal维护变量时,threadLocal为每个使用该变量的线程分配一个独立的变量副本. 接口方法: protected T initialValue( ...

  8. JavaSe:ThreadLocal

    JDK中有一个ThreadLocal类,使用很方便,但是却很容易出现问题.究其原因, 就是对ThreadLocal理解不到位.最近项目中,出现了内存泄漏的问题.其中就有同事在使用ThreadLocal ...

  9. 0041 Java学习笔记-多线程-线程池、ForkJoinPool、ThreadLocal

    什么是线程池 创建线程,因为涉及到跟操作系统交互,比较耗费资源.如果要创建大量的线程,而每个线程的生存期又很短,这时候就应该使用线程池了,就像数据库的连接池一样,预先开启一定数量的线程,有任务了就将任 ...

  10. ThreadLocal 源码剖析

    ThreadLocal是Java语言提供的用于支持线程局部变量的类.所谓的线程局部变量,就是仅仅只能被本线程访问,不能在线程之间进行共享访问的变量(每个线程一个拷贝).在各个Java web的各种框架 ...

随机推荐

  1. 20200311_解决Could not resolve host: mirrorlist.centos.org

    [root@localhost ~]# yum -y install wget 已加载插件:fastestmirror Determining fastest mirrors Could not re ...

  2. MySQL 连接为什么挂死了

    声明:本文为博主原创文章,由于已授权部分平台发表该文章(知乎.云社区),可能造成发布时间方面的困扰. 一.背景 近期由测试反馈的问题有点多,其中关于系统可靠性测试提出的问题令人感到头疼,一来这类问题有 ...

  3. mongo聚合操作

    1 mongodb的聚合是什么 聚合(aggregate)是基于数据处理的聚合管道,每个文档通过一个由多个阶段(stage)组成的管道,可以对每个阶段的管道进行分组.过滤等功能,然后经过一系列的处理, ...

  4. 第三十六章、PyQt输入部件:QAbstractSpinBox派生类QSpinBox、 QDoubleSpinBox、QDateTimeEdit、QDateEdit和QTimeEdit

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 一.概述 Designer输入部件中的Spin B ...

  5. 老猿学5G随笔:5G的三大业务场景eMBB、URLLC、mMTC

    5G的三大业务场景eMBB.URLLC.mMTC: eMBB:英文全称Enhanced Mobile Broadband,即增强移动宽带,是利用5G更好的网络覆盖及更高的传输速率来为用户提供更好的上网 ...

  6. PyQt(Python+Qt)学习随笔:Qt Designer中toolBar的orientation属性和iconSize属性

    orientation属性 orientation属性用于确认工具栏是水平方向还是垂直方向,这个属性对于QMainWindow中的工具栏来说没有意义,因为QMainWindow中的工具栏支持在上下左右 ...

  7. PyQt(Python+Qt)学习随笔:Qt Designer中主窗口对象unifiedTitleAndToolBarOnMac属性

    unifiedTitleAndToolBarOnMac 用于确认在mac操作系统上是否使用统一的标题和工具栏外观 有如下几个限制: 1.不支持使用带OpenGl内容的窗口,包括QGLWidget 和 ...

  8. 《深入理解计算机系统》实验一 —Data Lab

    本文是CSAPP第二章的配套实验,通过使用有限的运算符来实现正数,负数,浮点数的位级表示.通过完成这13个函数,可以使我们更好的理解计算机中数据的编码方式. 准备工作   首先去官网Lab Assig ...

  9. C# operator 关键字的用法

    operator 只要是运算符都能重载 operator 关键字的主要作用是用来重载运算符的,还可以用于类或结构中类型的自定义转换. 下面看个例子 class Feige { //定义两个全局变量 i ...

  10. 廖雪峰官网学习js 字符串

    操作字符串: length()           长度 totoLowerCase() 小写 toUpperCase()      大写 trim()            移除空白 charAt( ...