ThreadLocal适合用在哪些实际生产的场景中
适用场景
场景一 | 场景二 |
---|---|
ThreadLocal用作保存每个线程独享的对象 | ThreadLocal用作每个线程内需要独立保存信息以便其他方法更方便地获取该信息的场景(类似于全局变量的概念) |
通常用于保存线程不安全的工具类,典型的需要使用的类就是SimpleDateFormat。 | 每个线程内需要保存类似于全局变量的信息 (例如在拦截器中获取的用户信息)可以让不同方法直接使用避免参数传递的麻烦却不想被多线程共享 (因为不同线程获取到的用户信息不一样) |
每个Thread内都有自己的实例副本,且该副本只能由当前Thread访问到并使用,相当于每个线程内部的本地变量 | 用ThreadLocal保存一些业务内容(用户权限信息、从用户系统获取到的用户名、用户ID等) 这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的 在线程生命周期内,都通过这个静态ThreadLocal实例的get()方法取得自己set过的那个对象 避免了将这个对象(如user对象)作为参数传递的麻烦 |
java
import java.sql.Date;
import java.text.SimpleDateFormat;
public class ThreadLocalDemo02 {
public static void main(String[] args) throws InterruptedException {
int numberOfThreads = 1000;
for (int i = 0; i < numberOfThreads; i++) {
int finalI = i;
new Thread(() -> {
String date = new ThreadLocalDemo02().date(finalI);
System.out.println(date);
}).start();
}
// 确保主线程等待所有子线程完成
Thread.sleep(1000); // 增加等待时间以确保所有线程都能完成
}
public String date(int seconds) {
// 获取当前时间
Date date = new Date(1000 * seconds);
// 格式化时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.format(date);
}
}
java
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalDemo02 {
public static ExecutorService threadPool = Executors.newFixedThreadPool(16);
static SimpleDateFormat dateFormatHolder = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
int finalI = i;
threadPool.submit(new Runnable(){
@Override
public void run() {
String date = new ThreadLocalDemo02().date(finalI);
System.out.println(date);
}
});
}
threadPool.shutdown();
}
public String date(int seconds){
//获取当前时间
Date date = new Date(1000*seconds);
//格式化时间
String s = null;
// 使用synchronized会导致排队,其实也没有合理用到线程池
synchronized(ThreadLocalDemo02.class) {
s = dateFormatHolder.format(date);
}
return s;
}
}
java
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalDemo02 {
public static ExecutorService threadPool = Executors.newFixedThreadPool(16);
// 这个时候只创建了16个SimpleDateFormat对象
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
int finalI = i;
// 16个线程对应16个simpleDateFormat对象,每个线程有自己的副本,是线程安全的
threadPool.submit(new Runnable(){
@Override
public void run() {
String date = new ThreadLocalDemo02().date(finalI);
System.out.println(date);
}
});
}
threadPool.shutdown();
}
public String date(int seconds){
//获取当前时间
Date date = new Date(1000*seconds);
//格式化时间
return ThreadSafeFormatter.formatterHolder.get().format(date);
}
}
class ThreadSafeFormatter {
public static ThreadLocal<SimpleDateFormat> formatterHolder = new ThreadLocal<SimpleDateFormat>(){
protected SimpleDateFormat initialValue(){
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
}
java
public class ThreadLocalDemo03 {
public static void main(String[] args) {
new Service1().service1();
}
}
class Service1 {
public void service1() {
User user = new User("user1");
UserContextHolder.holder.set(user);
new Service2().service2();
}
}
class Service2 {
public void service2() {
User user = UserContextHolder.holder.get();
System.out.println("Service2 user name: " + user.getName());
new Service3().service3();
}
}
class Service3 {
public void service3() {
User user = UserContextHolder.holder.get();
System.out.println("Service3 user name: " + user.getName());
// 避免无限递归调用
}
}
class UserContextHolder {
public static ThreadLocal<User> holder = new ThreadLocal<>();
}
class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
ThreadLocal是不是用来解决共享资源的多线程访问的呢
答案:不是,ThreadLocal虽然可以用于解决多线程情况下的线程安全问题,但其资源不是共享的,而是每个线程独享的。可以在initialValue中new出自己线程独享的资源,而多个线程之间,它们所访问的对象本身是不共享的,自然就不存在任何并发问题。
如果需要放到ThreadLocal中的这个对象是共享的,是被 static 修饰的即使用了ThreadLocal并不能解决线程安全问题。
对于共享的变量,如果想要保证它的线程安全,应该用其他的方法比如说可以使用synchronized或者是加锁等其他的方法来解决线程安全问题而不是使用ThreadLocal,因为这不是ThreadLocal应该使用的场景
ThreadLocal和synchronized是什么关系?
当ThreadLocal用于解决线程安全问题时,也就是把一个对象给每个线程都生成一份独享的副本的,这种场景下,ThreadLocal和synchronized都可以理解为是用来保证线程安全的手段。但是效果和实现原理不同:
- ThreadLocal是通过让每个线程独享自己的副本,避免了资源的竞争
- synchronized 主要用于临界资源的分配,在同一时刻限制最多只有一个线程能访问该资源
相比于ThreadLocal而言,synchronized 的效率会更低一些,但是花费的内存也更少,在这种场景下,ThreadLocal和 synchronized 都可以达到线程安全的目的
对于ThreadLocal而言,它还有不同的使用场景,比如当ThreadLocal用于让多个类能更方便地拿到我们希望给每个线程独立保存这个信息的场景下时(比如每个线程都会对应一个用户信息,也就是user对象),在这种场景下,ThreadLocal侧重的是避免传参,此时ThreadLocal和 synchronized 是两个不同维度的工具。
多个ThreadLocal在Thread中的threadlocals是怎么存储的
三者的关系
[外链图片转存中...(img-uWLWNTd1-1734882917433)]
get()方法
[外链图片转存中...(img-ggtLn05S-1734882917433)]
[外链图片转存中...(img-JVxdHiAg-1734882917433)]
[外链图片转存中...(img-GyR5f8Jt-1734882917433)]
内存泄漏:每次用完ThreadLocal都要调用remove()
内存泄漏
当某个对象不再有用的时候,占用的内存却不能被回收。
[外链图片转存中...(img-HTkqftbA-1734882917433)]