述
了解了线程池之后,再来看一个常用的类,就是 ThreadLocal
这个类在面试中也是很常见的,下面来看一下这个类常见的使用场景
常见使用场景
ThreadLocal
比较常见的有两个地方
- 每个线程都需要一个独享的对象,通常是工具类,比如经常用的
SimpleDateFormat
和Random
,这两个类都不是线程安全的类,使用ThreadLocal
就可以保证线程安全 - 每个线程内需要保存一个全局变量,让不通的方法使用,这种场景可能需要把这个全局变量一级一级通过参数传递, 使用
ThreadLocal
可以避免这种参数传递的麻烦
下面分别看以下这两个场景
第一种场景
以常见的时间转换工具类 SimpleDateFormat
为例,写一个测试的示例,代码如下:
1 | @Slf4j |
代码很简单,就是创建一个 SimpleDateFormat
做时间转换,上面这段只有一个线程,而且 SimpleDateFormat
也是局部变量,所以是没有线程安全的问题的
当任务数量很大的时候,每个线程都会去执行创建 SimpleDateFormat
的实例,这就可能造成资源的浪费,我们可以把这个工具类转成共享变量去处理,如下:
1 | @Slf4j |
这样就只会创建一个对象,但是这里是会有线程安全的问题的, 这里可以去通过加锁同步的方式去实现,但是同步的方式效率太低了,所以这种情况下就可以去使用 ThreadLocal
,使每个线程都有自己的实例副本,不共享,然后我们对上面这个类做一个改造,代码如下
1 | @Slf4j |
这里首先创建一个类,ThreadSafeSimpleDateFormat
里面就存放了 ThreadLocal<SimpleDateFormat>
需要的时候通过这个类去拿
ThreadLocal
初始化有两种方法,一种是直接重写 initialValue()
方法,另一种是 ThreadLocal.withInitial()
用 lambda 实现,第二种更加简洁,但是效果一样的
以上就是 ThreadLocal
的第一种用法,然后看第二种场景
第二种方式
举个例子,假如有一个web请求,这个请求,会调用依次调用 service1()
service2()
service3()
这三个方法,这三个方法里面都需要一个user参数,通常情况就是在最上面一层获取到user对象,然后一级一级通过参数传递下去,这样就有点繁琐,这种情况下也可以通过 ThreadLocal
实现
代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42@Slf4j
public class ThreadLocalTest2 {
public static void main(String[] args) {
new Service1().process();
}
}
class Service1{
public void process(){
User user = new User("张三");
UserHolder.userThreadLocal.set(user);
new Service2().process();
}
}
@Slf4j
class Service2{
public void process(){
User user = UserHolder.userThreadLocal.get();
log.info("获取到用户信息:{}", user.getName());
new Service3().process();
}
}
@Slf4j
class Service3{
public void process(){
User user = UserHolder.userThreadLocal.get();
log.info("获取到用户信息:{}", user.getName());
}
}
class UserHolder{
public static ThreadLocal<User> userThreadLocal = new ThreadLocal<User>();
}
@AllArgsConstructor
@NoArgsConstructor
@Data
class User{
private String name;
}
这里主要看一下 UserHolder
,这里直接使用 new 来创建对象,并没有做初始化,之后在 Service1
中通过 set()
方法把对象传过去
按上面这种实现方式就可以实现不同的请求对应不通的线程,然后各个线程存储自己对应的 User
对象,这里主要强调的是同一个线程内的不同方法之间的共享
总结
通过两个案例对 ThreadLocal
的用法有一个基本的了解
总结一下 ThreadLocal
的两个作用
- 让某个需要用到的对象在线程间隔离,每个线程都有自己独立的对象
- 在同一个线程的任何方法中都可以轻松获取到该对象
然后是 ThreadLocal
设置对象的两种方式:
- 通过 initialValue 设置对象,然后这个是会懒加载的,在调用 get() 方法的时候才回去初始化
- 第二种就是通过
set()
方法设置
使用 ThreadLocal
的优点:
- 线程安全
- 不需要加锁,提高执行效率
- 高效利用内存,节省开销
- 免去繁琐的传参