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<Runnable>(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
结论:调用者运行的饱和策略实现了一种调节机制,当工作队列被填满时,下一个待执行的任务会在任务提交主线程中执行,由于任务执行需要一定得时间,在任务运行期间主线程将不能再提交任务,以此可以降低任务的提交速率,为线程池正确更多的时间来完成正在排队的任务。