java并发编程实践阅读笔记之线程池的饱和策略
使用java的任务管理框架的线程池执行任务时,线程池的任务等待队列被填满时,饱和策略开始发挥作用。ThreadPollExecutor的饱和策略通过setRejectedExecutionHandler来修改。JDK提供了4中饱和策略如下:
AbortPolicy是默认的饱和策略,该策略会抛出未检查异常RejectedExecutionException,调用者可以捕获这个异常,然后根据自己的需求编写代码。CallerRunsPolicy则提供了一种调节机制,该策略不会抛弃任务,也不会抛出异常,而是将任务的运行回退到任务调用者,在提交任务的线程中执行该任务。下列例子中模拟了调用者运行策略,线程池初始化大小为2,等待队列大小为2,当提交任务大于4个时,第5个任务就会在任务提交的主线程中运行。
public class MyCommand implements Runnable {
private String name;
public MyCommand(String name){
this.name = name;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" ," +
"name: "+name+","+new Date());
try {
Thread.sleep(5000);
} catch (InterruptedException execption) {
execption.printStackTrace();
}
}
}
使用调用者运行策略执行任务:
public class CallerRunTest {
private final ThreadPoolExecutor exec ;
public CallerRunTest(){
exec = new ThreadPoolExecutor(2,2,0L,TimeUnit.MICROSECONDS,
new LinkedBlockingQueue(2));
exec.setRejectedExecutionHandler(
new ThreadPoolExecutor.CallerRunsPolicy());
}
public static void main(String[] args) {
MyCommand c1 = new MyCommand("c1");
MyCommand c2 = new MyCommand("c2");
MyCommand c3 = new MyCommand("c3");
MyCommand c4 = new MyCommand("c4");
MyCommand c5 = new MyCommand("c5");
CallerRunTest c = new CallerRunTest();
c.submit(c1);
c.submit(c2);
c.submit(c3);
c.submit(c4);
c.submit(c5);
}
public void submit(Runnable command){
System.out.println(Thread.currentThread().getName()+" submit tast...");
exec.submit(command);
}
}
提交的任务打印一句任务名称,然后休眠5秒,线程池大小为2,等待队列大小为2 ,当第5个任务提交时,它会在主线程中执行,其他任务则都是由线程池调度运行。运行结果如下:
main submit tast...
main submit tast...
main submit tast...
main submit tast...
main submit tast...
pool-1-thread-1 ,name: c1,Mon Dec 15 15:55:02 CST 2014
main ,name: c5,Mon Dec 15 15:55:02 CST 2014
pool-1-thread-2 ,name: c2,Mon Dec 15 15:55:02 CST 2014
pool-1-thread-1 ,name: c4,Mon Dec 15 15:55:07 CST 2014
pool-1-thread-2 ,name: c3,Mon Dec 15 15:55:07 CST 2014
结论:调用者运行的饱和策略实现了一种调节机制,当工作队列被填满时,下一个待执行的任务会在任务提交主线程中执行,由于任务执行需要一定得时间,在任务运行期间主线程将不能再提交任务,以此可以降低任务的提交速率,为线程池正确更多的时间来完成正在排队的任务。