阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

使用ThreadLocal

267次阅读
没有评论

共计 2773 个字符,预计需要花费 7 分钟才能阅读完成。

多线程是 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);
    }
}

对于多任务,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();
        log();} finally {threadLocalUser.remove();
    }
}

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

void step1() {User u = threadLocalUser.get();
    log();
    printUser();}

void step2() {User u = threadLocalUser.get();
    checkUser(u.id);
}

void log() {User u = threadLocalUser.get();
    println(u.name);
}

注意到普通的方法调用一定是同一个线程执行的,所以,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() 获取当前线程绑定的用户名。

练习

练习使用 ThreadLocal。

下载练习

小结

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

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

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

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计2773字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中

星哥玩云

星哥玩云
星哥玩云
分享互联网知识
用户数
4
文章数
19356
评论数
4
阅读量
8272591
文章搜索
热门文章
星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛 NAS-6:抖音视频同步工具,视频下载自动下载保存 前言 各位玩 NAS 的朋友好,我是星哥!...
星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛 NAS-3:安装飞牛 NAS 后的很有必要的操作 前言 如果你已经有了飞牛 NAS 系统,之前...
飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛 NAS 中安装 Navidrome 音乐文件中文标签乱码问题解决、安装 FntermX 终端 问题背景 ...
星哥带你玩飞牛NAS-7:手把手教你免费内网穿透-Cloudflare tunnel

星哥带你玩飞牛NAS-7:手把手教你免费内网穿透-Cloudflare tunnel

星哥带你玩飞牛 NAS-7:手把手教你免费内网穿透 -Cloudflare tunnel 前言 大家好,我是星...
星哥带你玩飞牛NAS-2:飞牛配置RAID磁盘阵列

星哥带你玩飞牛NAS-2:飞牛配置RAID磁盘阵列

星哥带你玩飞牛 NAS-2:飞牛配置 RAID 磁盘阵列 前言 大家好,我是星哥之前星哥写了《星哥带你玩飞牛 ...
阿里云CDN
阿里云CDN-提高用户访问的响应速度和成功率
随机文章
开源MoneyPrinterTurbo 利用AI大模型,一键生成高清短视频!

开源MoneyPrinterTurbo 利用AI大模型,一键生成高清短视频!

  开源 MoneyPrinterTurbo 利用 AI 大模型,一键生成高清短视频! 在短视频内容...
星哥带你玩飞牛NAS-7:手把手教你免费内网穿透-Cloudflare tunnel

星哥带你玩飞牛NAS-7:手把手教你免费内网穿透-Cloudflare tunnel

星哥带你玩飞牛 NAS-7:手把手教你免费内网穿透 -Cloudflare tunnel 前言 大家好,我是星...
浏览器自动化工具!开源 AI 浏览器助手让你效率翻倍

浏览器自动化工具!开源 AI 浏览器助手让你效率翻倍

浏览器自动化工具!开源 AI 浏览器助手让你效率翻倍 前言 在 AI 自动化快速发展的当下,浏览器早已不再只是...
星哥带你玩飞牛NAS-1:安装飞牛NAS

星哥带你玩飞牛NAS-1:安装飞牛NAS

星哥带你玩飞牛 NAS-1:安装飞牛 NAS 前言 在家庭和小型工作室场景中,NAS(Network Atta...
飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛 NAS 中安装 Navidrome 音乐文件中文标签乱码问题解决、安装 FntermX 终端 问题背景 ...

免费图片视频管理工具让灵感库告别混乱

一言一句话
-「
手气不错
每年0.99刀,拿下你的第一个顶级域名,详细注册使用

每年0.99刀,拿下你的第一个顶级域名,详细注册使用

每年 0.99 刀,拿下你的第一个顶级域名,详细注册使用 前言 作为长期折腾云服务、域名建站的老玩家,星哥一直...
每天一个好玩的网站-手机博物馆-CHAZ 3D Experience

每天一个好玩的网站-手机博物馆-CHAZ 3D Experience

每天一个好玩的网站 - 手机博物馆 -CHAZ 3D Experience 一句话介绍:一个用 3D 方式重温...
开源MoneyPrinterTurbo 利用AI大模型,一键生成高清短视频!

开源MoneyPrinterTurbo 利用AI大模型,一键生成高清短视频!

  开源 MoneyPrinterTurbo 利用 AI 大模型,一键生成高清短视频! 在短视频内容...
星哥带你玩飞牛NAS-16:飞牛云NAS换桌面,fndesk图标管理神器上线!

星哥带你玩飞牛NAS-16:飞牛云NAS换桌面,fndesk图标管理神器上线!

  星哥带你玩飞牛 NAS-16:飞牛云 NAS 换桌面,fndesk 图标管理神器上线! 引言 哈...
国产开源公众号AI知识库 Agent:突破未认证号限制,一键搞定自动回复,重构运营效率

国产开源公众号AI知识库 Agent:突破未认证号限制,一键搞定自动回复,重构运营效率

国产开源公众号 AI 知识库 Agent:突破未认证号限制,一键搞定自动回复,重构运营效率 大家好,我是星哥,...