这篇文章主要介绍了 Java 线程池 ExecutorService 详解及实例代码的相关资料, 线程池减少在创建和销毁线程上所花的时间以及系统资源的开销. 如果不使用线程池, 有可能造成系统创建大量线程而导致消耗系统内存以及过度切换
Java 线程池 ExecutorService
1. 线程池
1.1 什么情况下使用线程池
单个任务处理的时间比较短.
将需处理的任务的数量大.
1.2 使用线程池的好处
减少在创建和销毁线程上所花的时间以及系统资源的开销.
如果不使用线程池, 有可能造成系统创建大量线程而导致消耗系统内存以及过度切换;
2.ExecutorService 和 Executors
2.1 简介
ExecutorService 是一个接口, 继承了 Executor,
- public interface ExecutorService extend Executor{
- }
Executor 也是一个接口, 该接口只包含一个方法:
- public interface Executor {
- void execute(Runnable command);
- }
Java 里面的线程池的顶级接口是 Excutor, 但是严格意义上来说>>Exector 并不是一个线程池, 而只是一个执行线程的工具, 真正的线程>池接口是 ExecutorService.
3.Executors
它是一个静态工厂类, 它能生产不同类型的线程池, 部分源码如下:
- public class Executors {
- //newFixedThreadPool
- public static ExecutorService newFixedThreadPool(int nThreads) {
- return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
- }
- //newCacheThreadPool
- public static ExecutorService newCachedThreadPool() {
- return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
- }
- //newScheduledThreadPool
- public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
- return new ScheduledThreadPoolExecutor(corePoolSize);
- }
- //newStringooo
- }
先看一个具体的例子, 用例子来说明它们之间的异同.
- package thread;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.ScheduledExecutorService;
- /**
- * Created by yang on 16-7-11.
- */
- public class Ch09_Executor {
- private static void run(ExecutorService threadPool) {
- for (int i = 1; i < 5; i++) {
- final int taskID=i;
- threadPool.execute(new Runnable() {
- @Override
- public void run() {
- for(int i=1;i<5;i++){
- try{
- Thread.sleep(20);
- }catch (InterruptedException e)
- {
- e.printStackTrace();
- }
- System.out.println("第"+taskID+"次任务的第"+i+"次执行");
- }
- }
- });
- }
- threadPool.shutdown();
- }
- public static void main(String[] args) {
- // 创建可以容纳 3 个线程的线程池
- ExecutorService fixedThreadPool= Executors.newFixedThreadPool(3);
- // 线程池的大小会根据执行的任务动态的分配
- ExecutorService cacheThreadPool=Executors.newCachedThreadPool();
- // 创建单个线程的线程池, 如果当前线程在执行任务时突然中断, 则会创建一个新的线程替换它继续执行.
- ExecutorService singleThreadPool=Executors.newSingleThreadExecutor();
- // 效果类似于 Timer 定时器
- ScheduledExecutorService scheduledThreadPool=Executors.newScheduledThreadPool(3);
- // run(fixedThreadPool); //(1)
- //run(cacheThreadPool); //(2)
- // run(singleThreadPool); //(3)
- // run(scheduledThreadPool); //(4)
- }
- }
4. 4 种常用的线程池
4.1 CachedThreadPool
CachedThreadPool 会创建一个缓存区, 将初始化的线程缓存起来, 会终止并且从缓存中移除已有 6 秒未被使用的线程.
如果线程有可用, 就使用之前创建好的线程. 如果线程没有可用的, 就新创建线程.
. 重用:
缓存型池子, 先看看池中有没有以前建立的线程, 如果有, 就 reuse, 如果没有, 就新建一个新的线程加入池中,
使用场景:
缓存型池子通常用于执行一些生存期很短的异步型任务, 因此在一些面向连接的 Daemon 型 SERVER 中用地不多.
超时:
能 reuse 的线程, 必须是 timeout IDLE 内的池中线程, 缺省 timeout 是 60s, 超过这个 IDLE 时长, 线程实例将被终止及移除池.
结束:
放入 CachedThreadPool 的线程不必担心其结束, 超过 TIMEOUT 不活动, 其会被自动终止.
实例解说:
去掉 (2) 的注释, 运行, 得到的运行结果如下:
第 1 次任务的第 1 次执行
第 3 次任务的第 1 次执行
第 2 次任务的第 1 次执行
第 4 次任务的第 1 次执行
第 3 次任务的第 2 次执行
第 1 次任务的第 2 次执行
第 2 次任务的第 2 次执行
第 4 次任务的第 2 次执行
第 3 次任务的第 3 次执行
第 1 次任务的第 3 次执行
第 2 次任务的第 3 次执行
第 4 次任务的第 3 次执行
第 3 次任务的第 4 次执行
第 2 次任务的第 4 次执行
第 4 次任务的第 4 次执行
第 1 次任务的第 4 次执行
从结果可以看出, 4 个任务是交替执行的.
4.2FixedThreadPool
在 FixedThreadPool 中, 有一个固定大小的池,
如果当前需要执行的任务超过池大小, 那么多出去的任务处于等待状态, 直到有空闲下来的线程执行任务
如果当前需要执行的任务小于池大小, 空闲线程不会被销毁.
重用:
fixedThreadPool 与 cacheThreadPool 差不多, 也是能 reuse 就用, 但不能随时建新的线程
固定数目
其独特之处在于, 任意时间点, 最多只能有固定数目的活动线程存在, 此时如果有新的线程要建立, 只能放在另外的队列中等待, 直到当前的线程中某个线程终止直接被移出池子
超时:
和 cacheThreadPool 不同, FixedThreadPool 没有 IDLE 机制
使用场景:
所以 FixedThreadPool 多数针对一些很稳定很固定的正规并发线程, 多用于服务器
源码分析:
从方法的源代码看, cache 池和 fixed 池调用的是同一个底层池, 只不过参数不同.
fixed 池线程数固定, 并且是 0 秒 IDLE(无 IDLE)
cache 池线程数支持 0-Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60 秒 IDLE
实例解说:
去掉 (1) 的注释, 运行结果如下:
第 1 次任务的第 1 次执行
第 3 次任务的第 1 次执行
第 2 次任务的第 1 次执行
第 1 次任务的第 2 次执行
第 3 次任务的第 2 次执行
第 2 次任务的第 2 次执行
第 1 次任务的第 3 次执行
第 3 次任务的第 3 次执行
第 2 次任务的第 3 次执行
第 1 次任务的第 4 次执行
第 3 次任务的第 4 次执行
第 2 次任务的第 4 次执行
第 4 次任务的第 1 次执行
第 4 次任务的第 2 次执行
第 4 次任务的第 3 次执行
第 4 次任务的第 4 次执行
创建了一个固定大小的线程池, 容量是为 3, 然后循环执行 4 个任务, 由输出结果可以看出, 前 3 个任务首先执行完, 然后空闲下来的线程去执行第 4 个任务.
4.3SingleThreadExecutor
SingleThreadExector 得到的是一个单个线程, 这个线程会保证你的任务执行完成.
单例线程, 任意时间池中只能有一个线程
如果当前线程意外终止, 会创建一个新的线程继续执行任务, 这和我们直接创建线程不同, 也和 newFixedThreadPool(1)不同.
用的是和 cache 池和 fixed 池相同的底层池, 但线程数目是 1-1,0 秒 IDLE(无 IDLE)
去掉 (3) 注释. 看执行结果如下:
第 1 次任务的第 1 次执行
第 1 次任务的第 2 次执行
第 1 次任务的第 3 次执行
第 1 次任务的第 4 次执行
第 2 次任务的第 1 次执行
第 2 次任务的第 2 次执行
第 2 次任务的第 3 次执行
第 2 次任务的第 4 次执行
第 3 次任务的第 1 次执行
第 3 次任务的第 2 次执行
第 3 次任务的第 3 次执行
第 3 次任务的第 4 次执行
第 4 次任务的第 1 次执行
第 4 次任务的第 2 次执行
第 4 次任务的第 3 次执行
第 4 次任务的第 4 次执行
四个任务是顺序执行的.
4.4 ScheduledThreadPool
ScheduledThreadPool 是一个固定大小的线程池, 与 FixedThreadPool 类似, 执行的任务是定时任务.
去掉 (4) 的注释得到的结果和 FixedThreadPool 得到的结果相同, ScheduledThreadPool 的主要没有在这里, 而是定时任务, 看下面这个例子:
- package thread;
- import java.util.concurrent.Executors;
- import java.util.concurrent.ScheduledExecutorService;
- import java.util.concurrent.TimeUnit;
- /**
- * Created by yang on 16-7-11.
- */
- public class MyScheduledTask implements Runnable {
- private String tname;
- public MyScheduledTask(String name){
- this.tname=name;
- }
- public void run(){
- System.out.println(tname+"任务时延时 2 秒执行!");
- }
- public static void main(String[] args) {
- ScheduledExecutorService scheduledPool= Executors.newScheduledThreadPool(2);
- ScheduledExecutorService singSchedulePool=Executors.newSingleThreadScheduledExecutor();
- MyScheduledTask mt1=new MyScheduledTask("mt1");
- MyScheduledTask mt2=new MyScheduledTask("mt2");
- // 以 scheduledThreadPool 启动 mt1 任务执行
- scheduledPool.schedule(mt1,2, TimeUnit.SECONDS);
- // 用 singlescheduledthreadPool 启动 mt2;
- singSchedulePool.schedule(mt2,2000,TimeUnit.MILLISECONDS);
- scheduledPool.shutdown();
- singSchedulePool.shutdown();
- }
- }
结果:
mt1 任务时延时 2 秒执行!
mt2 任务时延时 2 秒执行!
在程序运行 2 秒后, 才会有结果显示, 说明线程在 2 秒后执行的.
来源: http://www.phperz.com/article/18/0226/359292.html