第一章:多线程十四之ThreadLocal的基本使用
2023-11-01 16:56 阿方技术圈 阅读(65) 评论(0) 收藏 举报ThreadLocal位于JDK的java.lang核心包中。如果程序创建了一个ThreadLocal实例,那么在访问这个变量的值时,每个线程都会拥有一个独立的、自己的本地值。“线程本地变量”可以看成专属于线程的变量,不受其他线程干扰,保存着线程的专属数据。当线程结束后,每个线程所拥有的那个本地值会被释放。在多线程并发操作“线程本地变量”的时候,线程各自操作的是自己的本地值,从而规避了线程安全问题。
ThreadLocal的英文字面意思为“本地线程”,实际上ThreadLocal代表的是线程的本地变量,可能将其命名为ThreadLocalVariable更加容易让人理解。
ThreadLocal如何做到为每个线程存有一份独立的本地值呢?一个ThreadLocal实例可以形象地理解为一个Map(早期版本的ThreadLocal是这样设计的)。当工作线程Thread实例向本地变量保持某个值时,会以“Key-Value对”(即键-值对)的形式保存在ThreadLocal内部的Map中,其中Key为线程Thread实例,Value为待保存的值。当工作线程Thread实例从ThreadLocal本地变量取值时,会以Thread实例为Key,获取其绑定的Value。一个ThreadLocal实例内部结构的形象展示大致如图1-19所示。

具体代码如下
package com.crazymakercircle.multithread.basic.threadlocal;
// 省略import
public class ThreadLocalTest
{
@Data
static class Foo
{
//实例总数
static final AtomicInteger AMOUNT = new AtomicInteger(0);
//对象的编号
int index = 0;
//对象的内容
int bar = 10;
//构造器
public Foo()
{
index = AMOUNT.incrementAndGet(); //总数增加,并且给对象编号
}
@Override
public String toString()
{
return index + "@Foo{bar=" + bar + '}';
}
}
//定义线程本地变量
private static final ThreadLocal<Foo> LOCAL_FOO = new ThreadLocal<Foo>();
public static void main(String[] args) throws InterruptedException
{
//获取自定义的混合型线程池
ThreadPoolExecutor threadPool =
ThreadUtil.getMixedTargetThreadPool();
//提交5个任务,将会用到5个线程
for (int i = 0; i < 5; i++)
{
threadPool.execute(new Runnable()
{
@Override
public void run()
{
//获取“线程本地变量”中当前线程所绑定的值
if (LOCAL_FOO.get() == null)
{
//设置“线程本地变量”中当前线程所绑定的值
LOCAL_FOO.set(new Foo());
}
Print.tco("初始的本地值:" + LOCAL_FOO.get());
//每个线程执行10次
for (int i = 0; i < 10; i++)
{
Foo foo = LOCAL_FOO.get();
foo.setBar(foo.getBar() + 1); //值增1
sleepMilliSeconds(10);
}
Print.tco("累加10次之后的本地值:" + LOCAL_FOO.get());
//删除“线程本地变量”中当前线程所绑定的值
LOCAL_FOO.remove(); //这点对于线程池中的线程尤其重要
}
});
}
}
}
运行结果
[apppool-1-mixed-3]:初始的本地值:3@Foo{bar=10}
[apppool-1-mixed-4]:初始的本地值:4@Foo{bar=10}
[apppool-1-mixed-5]:初始的本地值:5@Foo{bar=10}
[apppool-1-mixed-2]:初始的本地值:1@Foo{bar=10}
[apppool-1-mixed-1]:初始的本地值:2@Foo{bar=10}
[apppool-1-mixed-1]:累加10次之后的本地值:2@Foo{bar=20}
[apppool-1-mixed-3]:累加10次之后的本地值:3@Foo{bar=20}
[apppool-1-mixed-5]:累加10次之后的本地值:5@Foo{bar=20}
[apppool-1-mixed-2]:累加10次之后的本地值:1@Foo{bar=20}
[apppool-1-mixed-4]:累加10次之后的本地值:4@Foo{bar=20}
ThreadLocal的使用场景
ThreadLocal是解决线程安全问题的一个较好的方案,它通过为每个线程提供一个独立的本地值去解决并发访问的冲突问题。在很多情况下,使用ThreadLocal比直接使用同步机制(如synchronized)解决线程安全问题更简单、更方便,且结果程序拥有更高的并发性。
ThreadLocal的使用场景大致可以分为以下两类:
(1)线程隔离ThreadLocal的主要价值在于线程隔离,ThreadLocal中的数据只属于当前线程,其本地值对别的线程是不可见的,在多线程环境下,可以防止自己的变量被其他线程篡改。另外,由于各个线程之间的数据相互隔离,避免了同步加锁带来的性能损失,大大提升了并发性的性能。ThreadLocal在线程隔离的常用案例为:可以为每个线程绑定一个用户会话信息、数据库连接、HTTP请求等,这样一个线程所有调用到的处理函数都可以非常方便地访问这些资源。常见的ThreadLocal使用场景为数据库连接独享、Session数据管理等。在“线程隔离”场景中,使用ThreadLocal的典型案例为:可以为每个线程绑定一个数据库连接,使得这个数据库连接为线程所独享,从而避免数据库连接被混用而导致操作异常问题。
(2)跨函数传递数据通常用于同一个线程内,跨类、跨方法传递数据时,如果不用ThreadLocal,那么相互之间的数据传递势必要靠返回值和参数,这样无形之中增加了这些类或者方法之间的耦合度。由于ThreadLocal的特性,同一线程在某些地方进行设置,在随后的任意地方都可以获取到。线程执行过程中所执行到的函数都能读写ThreadLocal变量的线程本地值,从而可以方便地实现跨函数的数据传递。使用ThreadLocal保存函数之间需要传递的数据,在需要的地方直接获取,也能避免通过参数传递数据带来的高耦合。在“跨函数传递数据”场景中使用ThreadLocal的典型案例为:可以为每个线程绑定一个Session(用户会话)信息,这样一个线程所有调用到的代码都可以非常方便地访问这个本地会话,而不需要通过参数传递。
浙公网安备 33010602011771号