ThreadLocal的作用与使用方法
ThreadLocal的作用与使用方法
1.ThreadLocal的作用:
按照官方解释:ThreadLocal的作用是提供线程内的局部变量,在多线程环境下访问时能保证各个线程内的ThreadLocal变量各自独立。也就是说,每个线程的ThreadLocal变量是自己专用的,其他线程是访问不到的。
Thread Local的方法:
1.threadLocal.set(T value) // 设置线程内初始化实例对象
2.threadLocal.get() // 获取线程内该实例对象
3.threadLocal.remove() // 移除线程内实例对象
简单理解:就是ThreadLocal提供的是一个线程内的局部变量,注意了啊,是线程内的局部变量,也就是说这个变量在各个线程内可能并不是相同的,线程局部变量是各个线程间不能共享的。
举个栗子: 以生活中最常见的考试场景为例。
考试时候老师发给每个人的考卷刚开始都是相同的吧,把这个试卷比作一个对象(全局变量),然后发下去给每个同学的都是这个试卷的副本(局部变量),每个同学都是一个线程,那么发下去的副本试卷就是每个同学个人的,可以理解成线程的局部变量。
然后在答题的过程中,各个同学填写的答案都不一样,相互之间也不知道彼此的答案是什么样的,都是各个同学专有的,这就是ThreadLocal修饰的局部变量,其它线程访问不到的道理。(此处不讨论作弊的问题哈,不能做杠精)
2.ThreadLocal使用注意点
1. 初始化线程中对象,防止空指针异常,方便设置对象属性值, 因为它默认是返回的空;
2. 不能用来存储大的对象
3. 使用private static final进行修饰,防止多实例时内存泄漏的问题
4. 分布式环境不能用ThreadLocal直接传递参数,因为不是同一个JVM;
3.ThreadLocal使用代码展示
场景:我们模拟同时有100个人发起请求访问某接口,然后输出100次租户id和租户name,即100个线程做同一件事情。事先分析:每个租户都有自己的id和名称,各自独立。最后我们输出租户id和租户名。为了方便演示我们租户名用“杨+线程id”表示。
1.首先我们定义一个租户Tenant 对象
package com.demo.spring.test.ThreadLocalUser;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
@Data
public class Tenant {
private String tenantId;
private String tenantName;
@Override
public String toString () {
return JSONObject.toJSONString(this);
}
}
2.创建一个全局ThreadLocal租户对象,并对租户进行初始化;
package com.demo.spring.test.ThreadLocalUser;
/**
* @Description: 创建上下文对象 ,用于传递参数,从前端传递到控制层等;
*/
public class TenantContext {
// 初始化线程中对象,防止空指针异常,方便设置对象属性值,因为它默认是返回的空;
// 不能用来存储大的对象
// 使用private static final进行修饰,防止多实例时内存泄漏的问题
// 分布式环境不能用ThreadLocal直接传递参数,因为不是同一个JVM;
private static final ThreadLocal<Tenant> threadLocal = new ThreadLocal<Tenant>(){
// 此处一定要对租户初始化实例对象,否则会报空指针异常,默认返回的空。
@Override
protected Tenant initialValue() {
return new Tenant();
}
};
// 定义获取线程中租户静态方法
public static Tenant getTenant(){
return threadLocal.get();
}
// 定义移除线程中的租户静态方法
public static void removeValue(Object key){
Tenant tenant = threadLocal.get();
if(null != tenant){
threadLocal.remove();
}
}
3.模拟具体的业务操作:给租户设置tenantId和设置tenantName
package com.demo.spring.test.ThreadLocalUser;
/**
* @Description: 为上下文中取出的租户设置tenantId和tenantName
*/
public class TenantIdAction {
// 从上下问获取租户对象,并给租户对象设置id值;id为当前线程的id;
public void setTenantId(){
Tenant tenant = TenantContext.getTenant();
tenant.setTenantId(this.getTenantId());
}
// 用"租户-线程id"来拼接租户名称
public void setTenantName(){
String name = "杨" + TenantContext.getTenant().getTenantId();
TenantContext.getTenant().setTenantName(name);
}
// 获取线程id作为租户id
public String getTenantId(){
return String.valueOf(Thread.currentThread().getId());
}
}
4.模拟前端的访问请求,我们定义线程具体执行逻辑
package com.demo.spring.test.ThreadLocalUser;
/**
* @Description: 模拟前端发送请求,进行参数的预封装;
*/
public class HtmlRequest implements Runnable {
private TenantIdAction tenantIdAction = new TenantIdAction();
@Override
public void run() {
// 上下文获取租户对象,此时租户对象为初始化对象,属性均为null;
Tenant tenant = TenantContext.getTenant();
// 模拟前端准备发送请求:开始设置租户的id
tenantIdAction.setTenantId();
tenantIdAction.setTenantName();
System.out.println("tenantId:"+tenant.getTenantId()+" --- "+tenant.getTenantName());
// 每个线程设置完属性以后,要清除掉上下问中的对象,是Tenant实例重新回到初始化状态;
// 可以保证每个线程取到的上下文里的初始对象都是一样的;
TenantContext.removeValue(tenant);
// 因为上一行的已经清空了上下文中的租户对象,所以下面这行代码打印的租户id和name应该都是null;需要看执行效果的同学放开下面代码注释即可。
// System.out.println("remove----tenantId : "+ TenantContext.getTenant().getTenantId() +"name : "+ TenantContext.getTenant().getTenantName());
}
}
5.最后main方法进行测试
package com.demo.spring.test.ThreadLocalUser;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestTenant {
public static void main(String[] args) {
HtmlRequest htmlRequest = new HtmlRequest();
// 新创建线程,线程id不会出现重复,都是新创建的
for(int i = 1; i <= 100; i++){
new Thread(htmlRequest).start();
}
}
}
6.执行结果
tenantId:50 --- 杨50
tenantId:43 --- 杨43
tenantId:38 --- 杨38
tenantId:35 --- 杨35
tenantId:55 --- 杨55
tenantId:54 --- 杨54
tenantId:27 --- 杨27
tenantId:47 --- 杨47
tenantId:15 --- 杨15
tenantId:19 --- 杨19
tenantId:46 --- 杨46
tenantId:14 --- 杨14
tenantId:51 --- 杨51
tenantId:22 --- 杨22
tenantId:18 --- 杨18
tenantId:23 --- 杨23
tenantId:26 --- 杨26
tenantId:59 --- 杨59
tenantId:30 --- 杨30
tenantId:31 --- 杨31
tenantId:34 --- 杨34
tenantId:39 --- 杨39
tenantId:42 --- 杨42
tenantId:62 --- 杨62
tenantId:58 --- 杨58
tenantId:74 --- 杨74
tenantId:71 --- 杨71
tenantId:79 --- 杨79
tenantId:86 --- 杨86
tenantId:91 --- 杨91
tenantId:98 --- 杨98
tenantId:99 --- 杨99
tenantId:102 --- 杨102
tenantId:103 --- 杨103
tenantId:106 --- 杨106
tenantId:107 --- 杨107
tenantId:110 --- 杨110
tenantId:111 --- 杨111
tenantId:63 --- 杨63
tenantId:66 --- 杨66
tenantId:67 --- 杨67
tenantId:70 --- 杨70
tenantId:75 --- 杨75
tenantId:78 --- 杨78
tenantId:82 --- 杨82
tenantId:95 --- 杨95
tenantId:90 --- 杨90
tenantId:83 --- 杨83
tenantId:94 --- 杨94
tenantId:105 --- 杨105
tenantId:81 --- 杨81
tenantId:76 --- 杨76
tenantId:72 --- 杨72
tenantId:73 --- 杨73
tenantId:20 --- 杨20
tenantId:69 --- 杨69
tenantId:13 --- 杨13
tenantId:16 --- 杨16
tenantId:12 --- 杨12
tenantId:17 --- 杨17
tenantId:21 --- 杨21
tenantId:24 --- 杨24
tenantId:25 --- 杨25
tenantId:28 --- 杨28
tenantId:29 --- 杨29
tenantId:32 --- 杨32
tenantId:33 --- 杨33
tenantId:36 --- 杨36
tenantId:37 --- 杨37
tenantId:41 --- 杨41
tenantId:40 --- 杨40
tenantId:44 --- 杨44
tenantId:45 --- 杨45
tenantId:48 --- 杨48
tenantId:49 --- 杨49
tenantId:53 --- 杨53
tenantId:52 --- 杨52
tenantId:56 --- 杨56
tenantId:57 --- 杨57
tenantId:60 --- 杨60
tenantId:61 --- 杨61
tenantId:64 --- 杨64
tenantId:68 --- 杨68
tenantId:65 --- 杨65
tenantId:77 --- 杨77
tenantId:80 --- 杨80
tenantId:84 --- 杨84
tenantId:85 --- 杨85
tenantId:88 --- 杨88
tenantId:89 --- 杨89
tenantId:92 --- 杨92
tenantId:93 --- 杨93
tenantId:96 --- 杨96
tenantId:97 --- 杨97
tenantId:100 --- 杨100
tenantId:101 --- 杨101
tenantId:104 --- 杨104
tenantId:108 --- 杨108
tenantId:109 --- 杨109
tenantId:87 --- 杨87
到此ThreadLocal的使用方法就展示结束了,如果有说的错误或者有问题的地方欢迎指正!
本文地址:https://blog.csdn.net/qq_37488998/article/details/109278028