1. ThreadLocal
1.1 回顾
多线程是Java实现多任务的基础:
- Thread:通过Thread来启动一个新的线程。Thread对象代表一个线程:调用Tread.currentThread()获取当前线程。
- ExecutorService、ScheduledThreadPool、Fork/Join:通过这些多线程框架实现多任务
多任务程序通常需要针对每个任务启动一个新的线程。
<img src="https://img2018.cnblogs.com/blog/1418970/201906/1418970-20190618213937414-2031620910.png" width=500" />
对于每个访问web程序的用户,我们都会启动一个新的线程,来处理这个用户的请求。当然也可以从线程池取出一个空闲的线程来处理。
1.2 ThreadLocal
问题:如何在一个线程内传递状态?
例如我们在一个线程处理过程中,经常需要调用不同的类来处理不同的功能,我们如何在这些方法中能够方便的获取到当前的用户?
JDK提供了ThreadLocal,在一个线程中传递同一个对象。
static ThreadLocal<String> threadLocalUser = new ThreadLocal<>();
threadLocalUser.set("Bob"); //给当前线程绑定指定的值
...
String current = threadLocalUser.get(); //调用get()方法可以 随时获取 当前线程已绑定的值
String current = threadLocalUser.get();
String current = threadLocalUser.get();
...
threadLocalUser.remove(); //把绑定的值从当前线程中解除
ThreadLocal典型的使用方式:
方法调用一定是同一个线程执行的,所以step1和printUser内部获取的User对象是同一个对象。
不同的线程关联的User是不同的对象,所以可以通过ThreadLocal来传递同一个对象。
便于理解,可以把ThreadLocal看成全局Map<Thread, Object>:每个线程获取ThreadLocal变量时,使用Thread自身作为key
Object ThreadLocalValue = threadLocalMap.get(Thread.currentThread());
1.3 清除ThreadLocal
注意:ThreadLocal一定要在finally中清除。
这是当前线程执行完相关代码以后,很有可能重新放入线程池中。
如果ThreadLocal没有被清除,这个线程在执行其他代码的时候,就会把上一次的状态带进去。
try{
UserContext.set(user);
...
}finally{
UserContext.remove();
}
2.示例
class User{ //表示当前的一个用户
String name;
int level;
public User(String name, int level){
this.name = name;
this.level = level;
}
}
class UserContext implements AutoCloseable{
static final ThreadLocal<User> context = new ThreadLocal<>(); //全局唯一静态变量
public static User getCurrentUser(){ //获取当前线程的ThreadLocal User
return context.get();
}
public UserContext(User user){ //初始化ThreadLocal的User
context.set(user);
}
@Override
public void close(){ //移除ThreadLocal关联的User
context.remove();
}
}
class ProcessThread extends Thread{
User user;
ProcessThread(User user){ //传入User对象
this.user = user;
}
public void run(){
try(UserContext ctx = new UserContext(user)){
//hello和checkLevel是没有参数的方法,但是我们仍然可以在方法内部获取到线程对象关联的User。
new Greeting().hello();
Level.checkLevel();
}
}
}
class Greeting{
void hello(){
User user = UserContext.getCurrentUser();
System.out.println("Hello,"+user.name+"!");
}
}
class Level{
static void checkLevel(){
User user = UserContext.getCurrentUser();
if(user.level>100){
System.out.println(user.name+" is a VIP user.");
}else{
System.out.println(user.name+" is a registered user.");
}
}
}
public class Main{
public static void main(String[] args) throws Exception{
Thread t1 = new ProcessThread(new User("Bob",120));
Thread t2 = new ProcessThread(new User("Alice",80));
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Main end");
}
}
3.总结:
- ThreadLocal表示线程的“局部变量”,它确保每个线程的ThreadLocal变量都是各自独立的
- ThreadLocal适合在一个线程的处理流程中保持上下文(避免了同一参数在所有方法中传递)
- 使用ThreadLocal要用try..finally结构