我们在使用异步操作的时候,一般会用到CompletableFuture这类,这是JUC包里的一个异步线程工具类,它里面会使用线程池,多个CompletableFuture,如果没有传入线程池的话,大多数都是使用默认的线程池
private static final Executor ASYNC_POOL = USE_COMMON_POOL ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
可以看到,线程池的这个属性被static和final修饰,当你使用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);
}
}