• 正文概述
  • 售后服务
  • ThreadLocal

    对于多任务,Java 标准库提供的线程池可以方便地执行这些任务,同时复用线程。那么如何在一个线程内传递状态?

    如下栗子,一个内部需要调用若干其他方法,同时传递参数 user。

    public void process(User user) {
        checkPermission(user);
        doWork(user);
        saveStatus(user);
        sendResponse(user);
    }
    

    这种在一个线程中,横跨若干方法调用,需要传递的对象,我们通常称之为上下文(Context),它是一种状态,可以是用户身份、任务信息等。

    给每个方法增加一个 context 参数非常麻烦,而且有些时候,如果调用链有无法修改源码的第三方库,User 对象就传不进去了。

    Java 标准库提供了一个特殊的 ThreadLocal,它可以在一个线程中传递同一个对象。

    ThreadLocal 实例通常总是以静态字段初始化如下:

    static ThreadLocal<String> threadLocalUser = new ThreadLocal<>();
    

    使用方式:

    void processUser(user) {
        try {
            threadLocalUser.set(user);
            step1();
            step2();
        } finally {
            threadLocalUser.remove();
        }
    }
    
    void step1() {
        User u = threadLocalUser.get();
        printUser();
    }
    
    void step2() {
        User u = threadLocalUser.get();
        checkUser(u.id);
    }
    

    注意到普通的方法调用一定是同一个线程执行的,所以,step1()step2() 方法内,threadLocalUser.get() 获取的 User 对象是同一个实例。

    实际上,可以把 ThreadLocal 看成一个全局 Map<Thread, Object>,每个线程获取 ThreadLocal 变量时,总是使用 Thread 自身作为 key:

    Object threadLocalValue = threadLocalMap.get(Thread.currentThread());
    

    因此,ThreadLocal 相当于给每个线程都开辟了一个独立的存储空间,各个线程的 ThreadLocal 关联的实例互不干扰。

    最后,特别注意 ThreadLocal 一定要在 finally 中清除。这是因为当前线程执行完相关代码后,很可能会被重新放入线程池中,如果 ThreadLocal 没有被清除,该线程执行其他代码时,会把上一次的状态带进去。

    为了保证能释放 ThreadLocal 关联的实例,我们可以通过 AutoCloseable 接口配合 try (resource) {...} 结构,让编译器自动为我们关闭。例如,一个保存了当前用户名的 ThreadLocal 可以封装为一个 UserContext 对象:

    public class UserContext implements AutoCloseable {
    
        static final ThreadLocal<String> ctx = new ThreadLocal<>();
    
        public UserContext(String user) {
            ctx.set(user);
        }
    
        public static String currentUser() {
            return ctx.get();
        }
    
        @Override
        public void close() {
            ctx.remove();
        }
    }
    

    使用方式:

    try (var ctx = new UserContext("Bob")) {
        // 可任意调用UserContext.currentUser():
        String currentUser = UserContext.currentUser();
    } // 在此自动调用UserContext.close()方法释放ThreadLocal关联对象
    

    这样就在 UserContext 中完全封装了 ThreadLocal,外部代码在 try (resource) {...} 内部可以随时调用 UserContext.currentUser() 获取当前线程绑定的用户名。

    小结

    ThreadLocal 空间换时间,synchronized 时间换空间。

    • ThreadLocal 表示线程的“局部变量”,它确保每个线程的 ThreadLocal 变量都是各自独立的;
    • ThreadLocal 适合在一个线程的处理流程中保持上下文(避免了同一参数在所有方法中传递);
    • 使用 ThreadLocal 要用 try ... finally 结构,并在 finally 中清除。

    DEMO

    ThreadLocal SimpleDateFormat

    /**
     * 10 个线程执行 1000 次打印格式化日期,每个线程有自己的格式化对象
     */
    public class ThreadLocalNormalUsage03 {
    
        private static ExecutorService threadPool = Executors.newFixedThreadPool(10);
    
        private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    
        public static void main(String[] args) {
            for (int i = 0; i < 1000; i++) {
                int finalI = i;
                threadPool.submit(() -> {
                    String date = new ThreadLocalNormalUsage03().date(finalI);
                    System.out.println(date);
                });
            }
            threadPool.shutdown();
        }
    
        private String date(int seconds) {
            Date date = new Date(1000 * seconds);
            SimpleDateFormat simpleDateFormat = dateFormatThreadLocal.get();
            return simpleDateFormat.format(date);
        }
    }
    
    本站所提供的部分资源来自于网络,版权争议与本站无关,版权归原创者所有!仅限用于学习和研究目的,不得将上述内容资源用于商业或者非法用途,否则,一切后果请用户自负。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容资源。如果上述内容资对您的版权或者利益造成损害,请提供相应的资质证明,我们将于3个工作日内予以删除。本站不保证所提供下载的资源的准确性、安全性和完整性,源码仅供下载学习之用!如用于商业或者非法用途,与本站无关,一切后果请用户自负!本站也不承担用户因使用这些下载资源对自己和他人造成任何形式的损失或伤害。如有侵权、不妥之处,请联系站长以便删除!
    金点网络 » Java 并发编程·ThreadLocal

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。
    是否提供免费更新服务?
    持续更新,永久免费
    是否经过安全检测?
    安全无毒,放心食用

    提供最优质的资源集合

    立即加入 友好社区
    ×