一.对ThreadLocal的理解
ThreadLocal为变量在每个线程中都创建了一个副本,一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的,也就说说每个线程都只能看到自己线程的值。
二.ThreadLocal的核心作用
实现线程范围的局部变量
三.ThreadLocla的API介绍
initialValue():返回此线程局部变量的当前线程的初始值,默认为null
get():返回当前线程的此线程局部变量的副本中的值
set(T value):将当前线程的此线程局部变量的副本设器为指定的值。
remove():删除此线程局部变量的当前线程的值。
withInitial(Supplier<? extends S> supplier):创建线程局部变量。
四.ThreadLocal应用
package com.tangbb.test1;
/**
* @DESCRIPTION:
* @USER: tangbingbing
* @DATE: 2023/3/13 9:49
*/
public class test1 {
private static ThreadLocal<Integer> num = new ThreadLocal<Integer>() {
// 重写这个方法,可以修改“线程变量”的初始值,默认是null
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
// 创建一号线程
new Thread(new Runnable() {
@Override
public void run() {
// 在一号线程中将ThreadLocal变量设置为1
num.set(1);
System.out.println("一号线程中ThreadLocal变量中保存的值为:" + num.get());
}
}).start();
// 创建二号线程
new Thread(new Runnable() {
@Override
public void run() {
num.set(2);
System.out.println("二号线程中ThreadLocal变量中保存的值为:" + num.get());
}
}).start();
//为了让一二号线程执行完毕,让主线程睡500ms
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("主线程中ThreadLocal变量中保存的值:" + num.get());
}
}
在类中创建一个静态的ThreadLocal变量,在主线程中创建两个线程,在这两个线程中分别设置ThreadLocal变量为1和2。然后等待一号和二号线程执行完毕后,在主线程中查看ThreadLocal变量的值
五.原理分析
每个Thread对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型。
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
六.回收举例(解决OOM问题)
为了防止OOM问题ThreadLocal在使用的时候要及时remove
package com.tangbb.test1;
/**
* @DESCRIPTION:
* @USER: tangbingbing
* @DATE: 2023/3/13 9:49
*/
public class test1 {
private static ThreadLocal<String> localVar = new ThreadLocal<String>();
static void print(String str) {
//打印当前线程中本地内存中本地变量的值
System.out.println(str + " :" + localVar.get());
//清除本地内存中的本地变量
localVar.remove();
}
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
public void run() {
test1.localVar.set("local_A");
print("A");
//打印本地变量
System.out.println("after remove : " + localVar.get());
}
},"A").start();
Thread.sleep(1000);
new Thread(new Runnable() {
public void run() {
test1.localVar.set("local_B");
print("B");
System.out.println("after remove : " + localVar.get());
}
},"B").start();
}
}
七.ThreadLocal常见的使用场景
ThreadLocal可以解决具有以下两个需求的场景:
1.每个线程需要有自己单独的实例
2.实例需要在多个方法中共享,但不希望被多线程共享
场景1
数据跨层传递(controller,service, dao)
比如说我们是一个用户系统,那么当一个请求进来的时候,一个线程会负责执行这个请求,然后这个请求就会依次调用service-1()、service-2()、service-3()、service-4(),这4个方法可能是分布在不同的类中的。
package com.kong.threadlocal;
public class ThreadLocalDemo05 {
public static void main(String[] args) {
User user = new User("jack");
new Service1().service1(user);
}
}
class Service1 {
public void service1(User user){
//给ThreadLocal赋值,后续的服务直接通过ThreadLocal获取就行了。
UserContextHolder.holder.set(user);
new Service2().service2();
}
}
class Service2 {
public void service2(){
User user = UserContextHolder.holder.get();
System.out.println("service2拿到的用户:"+user.name);
new Service3().service3();
}
}
class Service3 {
public void service3(){
User user = UserContextHolder.holder.get();
System.out.println("service3拿到的用户:"+user.name);
//在整个流程执行完毕后,一定要执行remove
UserContextHolder.holder.remove();
}
}
class UserContextHolder {
//创建ThreadLocal保存User对象
public static ThreadLocal<User> holder = new ThreadLocal<>();
}
class User {
String name;
public User(String name){
this.name = name;
}
}
执行的结果:
service2拿到的用户:jack
service3拿到的用户:jack
场景2
数据库连接,处理数据库事务