java多线程,CompletableFuture释放ThreadLocal资源

314 阅读2分钟

我们在使用异步操作的时候,一般会用到CompletableFuture这类,这是JUC包里的一个异步线程工具类,它里面会使用线程池,多个CompletableFuture,如果没有传入线程池的话,大多数都是使用默认的线程池

    private static final Executor ASYNC_POOL = USE_COMMON_POOL ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

可以看到,线程池的这个属性被staticfinal修饰,当你使用CompletableFuture的时候,这个线程池就被创建了。

那么这样会有什么问题呢?

有些人会在项目中,把CompletableFuture.runAsync()直接当做异步工具使用,有些业务相关的操作,他们涉及租户的过滤,会把访问的用户信息存在 ThreadLocal中,然后有些用了mybatis的服务,配置了插件,会拦截slq,给sql补上租户信息,会从ThreadLocal取用户信息。

那我们作为一个有操守的开发仔,是不是应该在有用到线程池的情况下,用完ThreadLocal的值以后,给它释放掉,防止别人用CompletableFuture的时候,被下毒... (当然这里指的是同一个ThreadLocal被使用的情况)

public class UserInfo {
    private static ThreadLocal threadLocal = new ThreadLocal();

    public static void setThreadLocal(String name){
        threadLocal.set(name);
    }
    public static String getThreadLocal(){
        return (String) threadLocal.get();
    }
}
public void testThreadPool() {
        public void testThreadPool() {
        //线程中都用了同一个ThreadLocal,当线程被复用时,threadLocal.get()就能获取到值
        CompletableFuture.runAsync(()-> UserInfo.setName("Leo"));
        CompletableFuture.runAsync(()-> System.out.println(UserInfo.getName()));
        CompletableFuture.runAsync(()-> System.out.println(UserInfo.getName()));
        CompletableFuture.runAsync(()-> System.out.println(UserInfo.getName()));
        CompletableFuture.runAsync(()-> System.out.println(UserInfo.getName()));
        CompletableFuture.runAsync(()-> System.out.println(UserInfo.getName()));
        CompletableFuture.runAsync(()-> System.out.println(UserInfo.getName()));
    }
    }

那我们应该怎么释放掉这个Thread呢?

public void testThreadPool() {
        public void testThreadPool() {
        //线程中都用了同一个ThreadLocal,当线程被复用时,threadLocal.get()就能获取到值
        CompletableFuture.runAsync(()-> {
            try {
                UserInfo.setName("Leo");
            }finally {
                UserInfo.remove();
            }
        });        
        CompletableFuture.runAsync(()-> System.out.println(UserInfo.getName()));
        CompletableFuture.runAsync(()-> System.out.println(UserInfo.getName()));
        CompletableFuture.runAsync(()-> System.out.println(UserInfo.getName()));
        CompletableFuture.runAsync(()-> System.out.println(UserInfo.getName()));
        CompletableFuture.runAsync(()-> System.out.println(UserInfo.getName()));
        CompletableFuture.runAsync(()-> System.out.println(UserInfo.getName()));
    }
    }

假如set的操作比较多呢?

public void testThreadPool() {
        CompletableFuture.runAsync(()-> {
            try {
                UserInfo.setName("Leo");
            }finally {
                UserInfo.remove();
            }
        });
        CompletableFuture.runAsync(()-> {
            try {
                UserInfo.setName("Leo2");
            }finally {
                UserInfo.remove();
            }
        });

        CompletableFuture.runAsync(()-> {
            try {
                UserInfo.setName("Leo3");
            }finally {
                UserInfo.remove();
            }
        });
    }

这样的大段重复的代码不是我们想要的吧?

那么可以改造成这样 定义一个工具类,在工具类中释放资源

class CompletableFutureHelper {
    // 这里假设你的业务需要从执行前放入当前线程的姓名,结束以后释放掉
    public static void runAsync(Runnable runnable) {
        try {
            UserInfo.setName("leo");
            CompletableFuture.runAsync(runnable);
        }catch (Exception e){
            throw e;
        } finally {
            UserInfo.remove();
        }
    }
    //这里则是使用你定义的userName
    public static void runAsync(String userName,Runnable runnable) {
        try {
            UserInfo.setName(userName);
            CompletableFuture.runAsync(runnable);
        }catch (Exception e){
            throw e;
        } finally {
            UserInfo.remove();
        }
    }
}

然后代码就变成了这样

    public void testThreadPool() {
        CompletableFutureHelper.runAsync("Leo",()-> {});
        CompletableFutureHelper.runAsync("Leo2",()-> {});
        CompletableFutureHelper.runAsync("Leo3",()-> {});
    }

假如我们用的是自己定义的线程池,我们还可以使用ThreadPoolExecutor线程池内部的方法去释放资源重写beforeExecute和afterExecute就行

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    //重写beforeExecute和afterExecute
                    beforeExecute(wt, task);
                    try {
                        task.run();
                        afterExecute(task, null);
                    } catch (Throwable ex) {
                        afterExecute(task, ex);
                        throw ex;
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }