线程池,是指管理一组同构工作线程的资源池。线程池是与工作队列密切相关的,其中在工作队列中保存了所有等待执行的任务。工作者线程的任务很简单:从工作队列中获取一个任务,执行任务然后返回线程池并等待下一任务。
“在线程池中执行任务”比“为每个任务分配一个线程”优势更多。通过重用现有的线程而不是创建新线程,可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销。另外一个额外的好处是,当请求到达时,工作线程通常已经存在,因此不会由于等待创建线程而延迟任务的执行,从而提高了响应性。通过适当调整线程池的大小,可以创建足够多的线程以便使处理器保持忙碌状态,同时还可以防止过多线程相互竞争资源而使应用程序耗尽内存或失败。(以上介绍来自书籍《Java并发编程实战》98~99页)
自Java 5 开始,Java 提供了自己的线程池(ThreadPoolExecutor)。
下边是API文档中关于构造函数的说明
线程池的基本大小(corePoolSize)也就是线程池的目标大小,即在没有任务执行时线程池的大小,并且只有在工作队列满的时候才会创建超出这个数量的线程。线程池的最大大小(maximumPoolSize)表示可同时活动的线程数量的上限。
当调用excute()方法时,线程池会做以下判断:
1,如果当前正在执行的线程数量少于corePoolSize,则该线程马上被执行
2,如果当前正在执行的线程数量大于或等于corePoolSize,则该线程池将添加到等待队列
3,当等待列队满了,而且当前正在执行的线程数量少于maximunPoolSize,则创建新的线程
4,当等待列队满了,而且当前正在执行的线程数量大于或等于maximunPoolSize,那么线程池就会抛出RejectedExecutionException异常,表示不能再接受新的任务了
当一个空闲线程等待时间超过keepAliveTime,而且当前正在运行的线程数量大于或等于corePoolSize,则空闲的线程会被删掉。线程池的线程数量最终会维持在corePoolSize大小。
构造函数中的参数BlockingQueue是一个接口,常用的实现类有 LinkedBlockingQueue 和 ArrayBlockingQueue。用 LinkedBlockingQueue 的好处在于没有大小限制。这样的话,因为队列不会满,所以 execute() 不会抛出异常,而线程池中运行的线程数也永远不会超过 corePoolSize 个,maximunPoolSize和keepAliveTime 参数也就没有实际意义了。
下边以具体的代码演示线程池的基本使用
为了清楚看出当前正在运行的线程情况,自定义了一个Runnable接口实现,构造函数多了一个名称表示线程名称,run()方法显示了当前执行线程的名称,并且使用一个死循环,以占线线程池的资源
class MyThread implements Runnable { private String name; public MyThread(String name){ this.name = name; } @Override public void run() { System.out.println(String.format("thread %s finished", this.name)); while(true);//不让线程结束 } }1,先演示构造函数以ArrayBlockingQueue的具体实现, 测试代码如下
注意:由于线程的运行使用了死循环,当全部打印出现后需要马上终止程序运行
private static void testArrayBlockingQueueExecutor(){ final int QUEUE_SIZE = 10;//阻塞队列的长度 BlockingQueue queue = new ArrayBlockingQueue(QUEUE_SIZE); ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 1, TimeUnit.HOURS, queue); final int THREAD_COUNT = 16; //需要运行的线程数量 for (int i = 0; i < THREAD_COUNT; i++) { executor.execute(new MyThread("Thread"+i)); } }
从运行结果可知,线程池corePoolSize大小为3,则一开始有0,1,2三号线程马上执行,而3到12号线程加入等待队列,队列满后,新创建3(maximunPoolSize减去corePoolSize)个线程来执行最后的13,14,15号线程。
继续考察其他情况可知,当线程数量THREAD_COUNT大于16的话,将抛出异常
2,接着演示构造函数以LinkedBlockingQueue的具体实现,测试代码如下
private static void testLinkedBlockingQueueExecutor(){ BlockingQueue queue = new LinkedBlockingQueue(); ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 1, TimeUnit.HOURS, queue); final int THREAD_COUNT = 16; //需要运行的线程数量 for (int i = 0; i < THREAD_COUNT; i++) { executor.execute(new MyThread("Thread"+i)); } executor.shutdown();//线程池关闭执行 }
从运行结果可知,线程池corePoolSize大小为3,则一开始有0,1,2三号线程马上执行,而由于等待队列永远也不会满,因此其他的线程也没有机会执行,除非前3个线程的run()方法执行结束。
3,最后一个例子,修改自定义MyThread类的run()方法,改为停顿一定时间后结束,则所有线程将按策略全部执行完毕
public void run() { System.out.println(String.format("thread %s finished", this.name)); // while(true);//不让线程结束 try{ Thread.sleep(100); }catch(Exception e){ } }其运行结果(部分截图)如下