从输出可以看出,线程池中的线程执行了10次,由于创建了固定的线程池就只有3个,与预期的只有一个线程才能拿到线程变量有很大差距;造成这种现象是因为线程复用导致的;
测试代码:
public class Demo {
public static void main(String[] args) throws Exception {
//创建可缓存线程池 无限大小
//ExecutorService executorService = Executors.newCachedThreadPool();
//可固定线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);//测试代码,实际操作不建议用此方法创建线程池
for (int i = 0; i < 10; i++) {
final int temp = i;
executorService.execute(new Runnable() {
@Override
public void run() {
try {
if (temp == 0) {
Ctx.setCtx("您好, yulang!" + temp);
}
Class<?> clazz = Class.forName("yulang.tools.ctx.ReflectSv");
Method method = clazz.getMethod("getCtx");
Object invoke = method.invoke(clazz.newInstance());
//线程池由于未创建新的线程,导致线程变量也是之前的内容,有了结论,修改就很简单了,一种直接在进入线程时设置为null,另一种是在使用后清空,
System.out.println(Thread.currentThread().getName() + ",拿到的线程变量为:" + invoke);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
}
public class Ctx {
//static防止GC回收
private static final ThreadLocal<String> CTX = new ThreadLocal<String>();
static String getCtx(){
return CTX.get();
}
static void setCtx(String object){
CTX.set(object);
}
static void remove(){
CTX.remove();
}
}
线程池由于未创建新的线程,导致线程变量也是之前的内容。
两种操作:
一、直接在进入线程时remove操作。
二、使用后清空。
实际中应使用:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
参数名、说明:
* corePoolSize 线程池维护线程的最少数量
* maximumPoolSize 线程池维护线程的最大数量
* keepAliveTime 线程池维护线程所允许的空闲时间
* workQueue 任务队列,用来存放我们所定义的任务处理线程
* threadFactory 线程创建工厂
* handler 线程池对拒绝任务的处理策略
其中拒绝策略包含:
1、CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); }}
这个策略显然不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。(开始我总不想丢弃任务的执行,但是对某些应用场景来讲,很有可能造成当前线程也被阻塞。如果所有线程都是不能执行的,很可能导致程序没法继续跑了。需要视业务情景而定吧。)
2、AbortPolicy:处理程序遭到拒绝将抛出运行时 RejectedExecutionException
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {throw new RejectedExecutionException();}
这种策略直接抛出异常,丢弃任务。(jdk默认策略,队列满并线程满时直接拒绝添加新任务,并抛出异常,所以说有时候放弃也是一种勇气,为了保证后续任务的正常进行,丢弃一些也是可以接收的,记得做好记录)
3、DiscardPolicy:不能执行的任务将被删除
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}
这种策略和AbortPolicy几乎一样,也是丢弃任务,只不过他不抛出异常。
4、DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) {e.getQueue().poll();e.execute(r); }}
该策略就稍微复杂一些,在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务,然后重新尝试运行该任务。这个策略需要适当小心。
workQueue:线程池中任务有三种排队策略:
- 直接提交。直接提交策略表示线程池不对任务进行缓存。新进任务直接提交给线程池,当线程池中没有空闲线程时,创建一个新的线程处理此任务。这种策略需要线程池具有无限增长的可能性。实现为:SynchronousQueue
- 有界队列。当线程池中线程达到corePoolSize时,新进任务被放在队列里排队等待处理。有界队列(如ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
- 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
流程图展示效果如图所示
线程销毁:
如果线程池中的线程数量大于 corePoolSize 时,如果某线程空闲时间超过 keepAliveTime,线程将被终止,直至线程池中的线程数目不大于 corePooIsize;
如果允许为核心线程池中的线程设置存活时间,那么核心线程池中的线程空闲时问超过 keepAliveTime,线程也会被终止。