在项目中,因线程安全考虑,经常使用到线程变量ThreadLocal。同时,基于线程资源管理的角度考虑,使用到线程池。当两个结合使用时,会遇到线程变量乱串的问题,具体的请见。
举例:
/** * 基于线程上下文的变量管理---每个线程对应的变量不同(实际项目中,Integer可以变为User等上下文相关信息) */ public class ThreadLocalClass { private static final ThreadLocal<Integer> var = new ThreadLocal<Integer>(){ @Override protected Integer initialValue() { return 0; } }; public static void setValue(int i){ var.set(i); } public static int getValue(){ return var.get(); } public static void remove(){ var.remove(); } public static void incremnet(){ var.set(var.get() + 1); } }
/**
*
* 当线程数小于核心线程数时,创建线程。
* 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
* 当线程数大于等于核心线程数,且任务队列已满
* 若线程数小于最大线程数,创建线程
* 若线程数等于最大线程数,抛出异常,拒绝任务
*
*线程变量的线程池使用混乱问题
* https://www.cnblogs.com/qifenghao/p/8977378.html
*/
public class ThreadLocalTest {
public static void main (String[] args){
ExecutorService exexutor= new ThreadPoolExecutor(2, 5,
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 100; i++) {
exexutor.execute(() -> {
long thread = Thread.currentThread().getId();
int before = ThreadLocalClass.getValue();
ThreadLocalClass.incremnet();
int after = ThreadLocalClass.getValue();
//核心部分 ThreadLocalClass.remove();
System.out.println(thread + ":before: " + before + ", after: " + after);
}
);
}
}
}
没有红色部分:
11:before: 0, after: 1
11:before: 1, after: 2
11:before: 2, after: 3
11:before: 3, after: 4
11:before: 4, after: 5
11:before: 5, after: 6
原因分析:线程池使用线程时,使用线程变量,这时因为线程池中的线程绑定的线程变量ThreadLocal在线程使用完后没有进行清理,线程池在复用线程的同时,也复用了线程变量。
改进:ThreadLocalClass.remove();
12:before: 0, after: 1
11:before: 0, after: 1
12:before: 0, after: 1
11:before: 0, after: 1
11:before: 0, after: 1
补充知识点:
线程池的知识点:
1、当线程数小于核心线程数时,创建线程。
2、 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3、 当线程数大于等于核心线程数,且任务队列已满
4、 若线程数小于最大线程数,创建线程 * 若线程数等于最大线程数,抛出异常,拒绝任务。