一 点睛
ThreadLocal 类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过 get 和 set 方法访问)时能保证各个线程的变量相对独立于其他的变量。ThreadLocal 实例通常来说都是private static 类型,用于关联线程和线程上下文。
我们可以得知 ThreadLocal 的作用:提供线程内的局部变量,不同线程之间不会相互干扰,这种变量在线程生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。
总结:
-
线程并发:在多线程并发场景下。
-
传递数据:可以通过 ThreadLocal 在同一线程,不同组件中传递公共变量。
-
线程隔离:每个线程变量独立,不会互相影响。
ThreadLocal,是Thread Local Variable(线程局部变量)的意思,也许将它命名为ThreadLocalVar更加合适。
线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。
ThreadLocal类的用法非常简单,它只提供了如下三个public方法:
-
ThreadLocal():创建 ThreadLocal 对象。
-
T get():返回此线程局部变量中当前线程副本中的值。
-
void remove():删除此线程局部变量中当前线程的值。
-
void set(T value):设置此线程局部变量中当前线程副本中的值。
二 实战
1 代码
class Account
{
/* 定义一个ThreadLocal类型的变量,该变量将是一个线程局部变量
每个线程都会保留该变量的一个副本 */
private ThreadLocal<String> name = new ThreadLocal<>();
// 定义一个初始化name成员变量的构造器
public Account(String str)
{
this.name.set(str);
// 下面代码用于访问当前线程的name副本的值
System.out.println("---" + this.name.get());
}
// name的setter和getter方法
public String getName()
{
return name.get();
}
public void setName(String str)
{
this.name.set(str);
}
}
class MyTest extends Thread
{
// 定义一个Account类型的成员变量
private Account account;
public MyTest(Account account, String name)
{
super(name);
this.account = account;
}
public void run()
{
// 循环10次
for (int i = 0 ; i < 10 ; i++)
{
// 当i == 6时输出将账户名替换成当前线程名
if (i == 6)
{
account.setName(getName());
}
// 输出同一个账户的账户名和循环变量
System.out.println(account.getName()
+ " 账户的i值:" + i);
}
}
}
public class ThreadLocalTest
{
public static void main(String[] args)
{
// 启动两条线程,两条线程共享同一个Account,
// 主线程中有一个Account的name,线程甲和线程乙中也各有一个Account的name,三者互不干扰
Account at = new Account("初始名");
/*
虽然两条线程共享同一个账户,即只有一个账户名
但由于账户名是ThreadLocal类型的,所以每条线程
都完全拥有各自的账户名副本,所以从i == 6之后,将看到两条
线程访问同一个账户时看到不同的账户名。
*/
new MyTest(at , "线程甲").start();
new MyTest(at , "线程乙").start ();
}
}
2 运行
---初始名
null 账户的i值:0
null 账户的i值:0
null 账户的i值:1
null 账户的i值:1
null 账户的i值:2
null 账户的i值:2
null 账户的i值:3
null 账户的i值:3
null 账户的i值:4
null 账户的i值:4
null 账户的i值:5
线程甲 账户的i值:6
线程甲 账户的i值:7
线程甲 账户的i值:8
线程甲 账户的i值:9
null 账户的i值:5
线程乙 账户的i值:6
线程乙 账户的i值:7
线程乙 账户的i值:8
线程乙 账户的i值:9
3 说明
账号名实际有3个副本,主线程一个,另外启动的两个线程各一个,它们的值互不干扰,每个线程完全拥有自己的ThreadLocal变量,这就是ThreadLocal的用途。
三 对比
1 不用 ThreadLocal
a 代码
package threadLocal;
/**
* @className: ThreadLocalDemoWithoutThreadLocal
* @description: 线程隔离
* 在多线程并发的场景下,每个线程中的变量都是相互独立
* 线程A:设置(变量1) 获取(变量1)
* 线程B:设置(变量2) 获取(变量2)
* @date: 2021/12/22
* @author: cakin
*/
public class ThreadLocalDemoWithoutThreadLocal {
// 变量
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static void main(String[] args) {
ThreadLocalDemoWithoutThreadLocal demo = new ThreadLocalDemoWithoutThreadLocal();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
// 每个线程:存一个变量,过一会,取出这个变量
demo.setContent(Thread.currentThread().getName() + "的数据");
System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
}).start();
}
}
}
b 测试结果
Thread-0--->Thread-1的数据
Thread-2--->Thread-1的数据
Thread-4--->Thread-4的数据
Thread-3--->Thread-3的数据
Thread-1--->Thread-1的数据
Thread-5--->Thread-5的数据
Thread-6--->Thread-6的数据
Thread-7--->Thread-7的数据
Thread-9--->Thread-9的数据
Thread-8--->Thread-8的数据
2 使用 ThreadLocal
a 代码
package threadLocal;
/**
* @className: ThreadLocalDemoWithThreadLocal
* @description: 线程隔离
* 在多线程并发的场景下,每个线程中的变量都是相互独立
* 线程A:设置(变量1) 获取(变量1)
* 线程B:设置(变量2) 获取(变量2)
* ThreadLocal:
* set():将变量绑定到当前线程中
* get():获取当前线程绑定的变量
* @date: 2021/12/22
* @author: cakin
*/
public class ThreadLocalDemoWithThreadLocal {
ThreadLocal<String> t1 = new ThreadLocal<>();
// 变量
private String content;
public String getContent() {
return t1.get();
}
public void setContent(String content) {
// 变量 content 绑定到当前线程
t1.set(content);
}
public static void main(String[] args) {
ThreadLocalDemoWithThreadLocal demo = new ThreadLocalDemoWithThreadLocal();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
// 每个线程:存一个变量,过一会,取出这个变量
demo.setContent(Thread.currentThread().getName() + "的数据");
System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
}).start();
}
}
}
b 测试
Thread-1--->Thread-1的数据
Thread-2--->Thread-2的数据
Thread-0--->Thread-0的数据
Thread-3--->Thread-3的数据
Thread-4--->Thread-4的数据
Thread-6--->Thread-6的数据
Thread-5--->Thread-5的数据
Thread-8--->Thread-8的数据
Thread-7--->Thread-7的数据
Thread-9--->Thread-9的数据
四 复杂对象的使用
1 说明
我们在实际开发时,常常会用到列表对象,这里举一个例子。
2 代码
package threadLocal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @className: ThreadLocalDemoAboutListAndObj
* @description: 线程隔离
* 在多线程并发的场景下,每个线程中的变量都是相互独立
* 线程A:设置(变量1) 获取(变量1)
* 线程B:设置(变量2) 获取(变量2)
* ThreadLocal:
* set():将变量绑定到当前线程中
* get():获取当前线程绑定的变量
* @date: 2021/12/22
* @author: cakin
*/
public class ThreadLocalDemoAboutListAndObj {
ThreadLocal<List<Date>> t1 = new ThreadLocal<>();
// 比较复杂的对象
private static List<Date> list = new ArrayList<>(1);
public List<Date> getList() {
return t1.get();
}
public void setList(List<Date> list) {
t1.set(list);
}
public static void main(String[] args) {
ThreadLocalDemoAboutListAndObj demo = new ThreadLocalDemoAboutListAndObj();
for (int i = 0; i < 10; i++) {
Date date = new Date(2021, 12, i);
List<Date> objects = new ArrayList<>();
objects.add(date);
new Thread(() -> {
demo.setList(objects);
System.out.println(Thread.currentThread().getName() + "--->" + demo.getList());
}).start();
}
}
}
3 测试
Thread-3--->[Tue Jan 03 00:00:00 CST 3922]
Thread-9--->[Mon Jan 09 00:00:00 CST 3922]
Thread-2--->[Mon Jan 02 00:00:00 CST 3922]
Thread-4--->[Wed Jan 04 00:00:00 CST 3922]
Thread-1--->[Sun Jan 01 00:00:00 CST 3922]
Thread-7--->[Sat Jan 07 00:00:00 CST 3922]
Thread-8--->[Sun Jan 08 00:00:00 CST 3922]
Thread-5--->[Thu Jan 05 00:00:00 CST 3922]
Thread-0--->[Sat Dec 31 00:00:00 CST 3921]
Thread-6--->[Fri Jan 06 00:00:00 CST 3922]
五 参考
[NIO与Netty] ThreadLocal详解_zkp_java的专栏-CSDN博客_nio threadlocal
Java中的ThreadLocal详解 - 夏末秋涼 - 博客园