廖雪峰Java11多线程编程-4线程工具类-1ThreadLocal

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结构
posted on 2019-06-18 21:07  singleSpace  阅读(586)  评论(0编辑  收藏  举报