Java 并发:Executors 和线程池

本文译自:Java Concurrency – Part 7 : Executors and thread pools

让我们开始来从入门了解一下 Java 的并发编程。

本文主要介绍如何开始创建线程以及管理线程池,在 Java 语言中,一个最简单的线程如下代码所示:

Runnable runnable = new Runnable(){
   public void run(){
      System.out.println("Run");
   }
}

可通过下面一行代码来启动这个线程:

new Thread(runnable).start();

这是一个再简单不过的例子了,但如果你有许多需要长时间运行的任务同时执行,并需要等所有的这些线程都执行完毕,还想得到一个返回值,那么这就有点小小难度了。但 Java 已经有解决方案给你,那就是 Executors ,一个简单的类可以让你创建线程池和线程工厂。

一个线程池使用类 ExecutorService 的实例来表示,通过 ExecutorService 你可以提交任务,并进行调度执行。下面列举一些你可以通过 Executors 类来创建的线程池的类型:

  • Single Thread Executor : 只有一个线程的线程池,因此所有提交的任务是顺序执行,代码:Executors.newSingleThreadExecutor()
  • Cached Thread Pool : 线程池里有很多线程需要同时执行,老的可用线程将被新的任务触发重新执行,如果线程超过60秒内没执行,那么将被终止并从池中删除,代码:Executors.newCachedThreadPool()
  • Fixed Thread Pool : 拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待,代码:Executors.newFixedThreadPool()
  • Scheduled Thread Pool : 用来调度即将执行的任务的线程池,代码:Executors.newScheduledThreadPool()
  • Single Thread Scheduled Pool : 只有一个线程,用来调度执行将来的任务,代码:Executors.newSingleThreadScheduledExecutor()

一旦你创建了一个线程池,你就可以往池中通过不同的方法提交执行任务,可提交 Runnable 或者 Callable 到线程池中,该方法返回一个 Future 实例表示任务的状态,如果你提交一个 Runnable ,那么如果任务完成后 Future 对象返回 null。

例如,你编写下面的 Callable:

private final class StringTask extends Callable<String>{
   public String call(){
      //Long operations
 
      return "Run";
   }
}

如果你想使用4个线程来执行这个任务10次,那么代码如下:

ExecutorService pool = Executors.newFixedThreadPool(4);
 
for(int i = 0; i < 10; i++){
   pool.submit(new StringTask());
}

但你必须手工的关闭线程池来结束所有池中的线程:

pool.shutdown();

如果你不这么做,JVM 并不会去关闭这些线程;另外你可以使用 shutdownNow() 的方法来强制关闭线程池,那么执行中的线程也会被中断,所有尚未被执行的任务也将不会再执行。

但这个例子中,你无法获取任务的执行状态,因此我们需要借助 Future 对象:

 

ExecutorService pool = Executors.newFixedThreadPool(4);
 
List<Future<String>> futures = new ArrayList<Future<String>>(10);
 
for(int i = 0; i < 10; i++){
   futures.add(pool.submit(new StringTask()));
}
 
for(Future<String> future : futures){
   String result = future.get();
 
   //Compute the result
}
 
pool.shutdown();

 

不过这段代码稍微有点复杂,而且有不足的地方。如果第一个任务耗费非常长的时间来执行,然后其他的任务都早于它结束,那么当前线程就无法在第一个任务结束之前获得执行结果,但是别着急,Java 为你提供了解决方案——CompletionService。

一个 CompletionService 就是一个服务,用以简化等待任务的执行结果,实现的类是 ExecutorCompletionService,该类基于 ExecutorService,因此我们可试试下面的代码:

ExecutorService threadPool = Executors.newFixedThreadPool(4);
CompletionService<String> pool = new ExecutorCompletionService<String>(threadPool);
 
for(int i = 0; i < 10; i++){
   pool.submit(new StringTask());
}
 
for(int i = 0; i < 10; i++){
   String result = pool.take().get();
 
   //Compute the result
}
 
threadPool.shutdown();
通过这段代码,我们可以根据执行结束的顺序获取对应的结果,而无需维护一个 Future 对象的集合。

这就是本文的全部,通过 Java 为我们提供的各种工具,可以方便的进行多任务的编程,通过使用 Executors、ExecutorService 以及 CompletionService 等工具类,我们可以创建复杂的并行任务执行算法,而且可以轻松改变线程数。

希望这篇短文能有助于你对并发编程的理解。


---


Executor 是 java5 下的一个多任务并发执行框架(Doug Lea),可以建立一个类似数据库连接池的线程池来执行任务。这个框架主要由三个接口和其相应的具体类组成。Executor、 ExecutorService 和 ScheduledExecutorService 。
   1 、 Executor 接口:是用来执行 Runnable 任务的;它只定义一个方法- execute(Runnable command);执行 Ruannable 类型的任务。
   2 、 ExecutorService 接口: 继承Executor接口,提供了执行Callable任务和中止任务执行的服务。
   3 、 ScheduledExecutorService 接口:继承 ExecutorService 接口,提供了按排程执行任务的服务。
   4 、 Executors 类:为了方便使用, 建议使用 Executors的工具类来得到 Executor 接口的具体对象。

 Executors 类有几个重要的方法,在这里简明一下:
    1 、 callable(Runnable task):      将 Runnable 的任务转化成 Callable 的任务
    2 、 newSingleThreadExecutor():    产生一个 ExecutorService 对象,这个对象只有一个线程可用来执行任务,若任务多于一个,任务将按先后顺序执行。
    3 、 newCachedThreadPool():        产生一个 ExecutorService 对象,这个对象带有一个线程池,线程池的大小会根据需要调整,线程执行完任务后返回线程池,供执行下一次任务使用。
    4 、 newFixedThreadPool(int poolSize):  产生一个 ExecutorService 对象,这个对象带有一个大小为 poolSize 的线程池,若任务数量大于 poolSize ,任务会被放在一个 queue 里顺序执行。
    5 、 newSingleThreadScheduledExecutor(): 产生一个 ScheduledExecutorService 对象,这个对象的线程池大小为 1 ,若任务多于一个,任务将按先后顺序执行。
    6 、 newScheduledThreadPool(int poolSize): 产生一个 ScheduledExecutorService 对象,这个对象的线程池大小为 poolSize ,若任务数量大于 poolSize ,任务会在一个 queue 里等待执行 。

  有关Executor框架其它类的说明请参看JAVA 5 的 API文档


  下面是几个简单的例子,用以示例Executors中几个主要方法的使用。
    1、 Task.java 任务
    2、 SingleThreadExecutorTest.java  单线程执行程序的测试
    3、 CachedThreadPoolTest.java      线程池线程执行程序的测试
    4、 FixedThreadPoolTest.java       线程池线程执行程序的测试(线程数固定)
    5、 DaemonThreadFactory.java       守护线程生成工厂
    6、 MaxPriorityThreadFactory.java  大优先级线程生成工厂
    7、 MinPriorityThreadFactory.java  小优先级线程生成工厂
    8、 ThreadFactoryExecutorTest.java 在自定义线程生成工厂下的测试

 

    1、 Task.java 任务

Java代码   收藏代码
  1. package com.thread;  
  2.   
  3. //可执行任务  
  4. public class Task implements Runnable {  
  5.     // 中断信号  
  6.     volatile boolean stop = false;  
  7.   
  8.     // 该任务执行的次数  
  9.     private int runCount = 0;  
  10.   
  11.     // 任务标识  
  12.     private int taskId;  
  13.   
  14.     public Task(int taskId) {  
  15.         this.taskId = taskId;  
  16.         System.out.println("Create Task-" + taskId);  
  17.     }  
  18.   
  19.     // 执行任务  
  20.     public void run() {  
  21.   
  22.         while (!stop) {  
  23.             try {  
  24.                 Thread.sleep(10);  
  25.             } catch (InterruptedException e) {  
  26.                 System.out.println("Task interrupted...");  
  27.             }  
  28.   
  29.             // 线程运行3次后,中断信号置为true  
  30.             if (++runCount == 3)  
  31.                 stop = true;  
  32.   
  33.             // 输出一些语句  
  34.             System.out.println("" + Thread.currentThread().toString()  
  35.                     + "\t\t\t\t execute Task-" + taskId + "'s " + runCount  
  36.                     + "th run. ");  
  37.   
  38.         }  
  39.     }  
  40. }  

 

    2、 SingleThreadExecutorTest.java  单线程执行程序的测试

 

Java代码   收藏代码
  1. package com.thread;  
  2.   
  3. import java.util.concurrent.ExecutorService;  
  4. import java.util.concurrent.Executors;  
  5.   
  6. public class SingleThreadExecutorTest {  
  7.   
  8.   public static void main(String[] args) {  
  9.     try {  
  10.       // 创建一个单线程执行程序  
  11.       ExecutorService executorService = Executors.newSingleThreadExecutor();  
  12.       for (int i =1; i <= 3; i++) {  
  13.         executorService.execute(new Task(i));  
  14.       }  
  15.       executorService.shutdown();  
  16.        
  17.     } catch (Exception e) {}  
  18.   }  
  19. }  

 

 3、 CachedThreadPoolTest.java      线程池线程执行程序的测试

Java代码   收藏代码
  1. package com.thread;  
  2.   
  3. import java.util.concurrent.ExecutorService;  
  4. import java.util.concurrent.Executors;  
  5.   
  6. public class CachedThreadPoolTest {  
  7.   
  8.   public static void main(String[] args) {  
  9.     try {  
  10.       // 建新线程的线程池,如果之前构造的线程可用则重用它们  
  11.       ExecutorService executorService = Executors.newCachedThreadPool();  
  12.       for (int i =1; i <= 4; i++) {  
  13.         executorService.execute(new Task(i));  
  14.       }  
  15.       executorService.shutdown();  
  16.        
  17.     } catch (Exception e) {}  
  18.   }  
  19. }  

  4、 FixedThreadPoolTest.java       线程池线程执行程序的测试(线程数固定)

Java代码   收藏代码
  1. package com.thread;  
  2.   
  3. import java.util.concurrent.ExecutorService;  
  4. import java.util.concurrent.Executors;  
  5.   
  6. public class FixedThreadPoolTest {  
  7.   
  8.   public static void main(String[] args) {  
  9.     try {  
  10.       // 创建固定线程数的线程池,以共享的无界队列方式来运行这些线程  
  11.       ExecutorService executorService = Executors.newFixedThreadPool(2);  
  12.       for (int i =1; i <= 5; i++) {  
  13.         executorService.execute(new Task(i));  
  14.       }  
  15.       executorService.shutdown();  
  16.        
  17.     } catch (Exception e) {}  
  18.   }  
  19. }  

     5、 DaemonThreadFactory.java       守护线程生成工厂

Java代码   收藏代码
  1. package com.thread;  
  2.   
  3. import java.util.concurrent.ThreadFactory;  
  4.   
  5. public class DaemonThreadFactory implements ThreadFactory {  
  6.    
  7.   //创建一个守护线程  
  8.   public Thread newThread(Runnable r) {  
  9.     Thread t = new Thread(r);  
  10.     t.setDaemon(true);  
  11.     return t;  
  12.   }  
  13. }  


---------------------



Java里ExecutorService里shutdown



当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态,以后不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。 与它相似的还有一个shutdownNow(),它通过调用Thread.interrupt来实现线程的立即退出。




示例

[java]  view plain  copy
 print ?
  1. import java.util.concurrent.ExecutorService;  
  2. import java.util.concurrent.Executors;  
  3. import java.util.concurrent.ScheduledExecutorService;  
  4.   
  5. public class Ch09_Executor {  
  6.       
  7.    private static void run(ExecutorService threadPool) {  
  8.     for(int i = 1; i < 5; i++) {    
  9.             final int taskID = i;    
  10.             threadPool.execute(new Runnable() {    
  11.                 @Override  
  12.         public void run() {    
  13.                     for(int i = 1; i < 5; i++) {    
  14.                         try {    
  15.                             Thread.sleep(20);// 为了测试出效果,让每次任务执行都需要一定时间    
  16.                         } catch (InterruptedException e) {    
  17.                             e.printStackTrace();    
  18.                         }    
  19.                         System.out.println("第" + taskID + "次任务的第" + i + "次执行");    
  20.                     }    
  21.                 }    
  22.             });    
  23.         }    
  24.         threadPool.shutdown();// 任务执行完毕,关闭线程池    
  25.    }  
  26.       
  27.     public static void main(String[] args) {  
  28.         // 创建可以容纳3个线程的线程池  
  29.         ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);  
  30.           
  31.         // 线程池的大小会根据执行的任务数动态分配  
  32.         ExecutorService cachedThreadPool = Executors.newCachedThreadPool();  
  33.           
  34.             // 创建单个线程的线程池,如果当前线程在执行任务时突然中断,则会创建一个新的线程替代它继续执行任务    
  35.         ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();  
  36.           
  37.         // 效果类似于Timer定时器  
  38.         ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);  
  39.           
  40.         run(fixedThreadPool);  
  41. //      run(cachedThreadPool);  
  42. //      run(singleThreadPool);  
  43. //      run(scheduledThreadPool);  
  44.     }  
  45.   
  46. }  

CachedThreadPool

CachedThreadPool会创建一个缓存区,将初始化的线程缓存起来。会终止并且从缓存中移除已有60秒未被使用的线程。

如果线程有可用的,就使用之前创建好的线程,

如果线程没有可用的,就新创建线程。


  • 重用:缓存型池子,先查看池中有没有以前建立的线程,如果有,就reuse;如果没有,就建一个新的线程加入池中
  • 使用场景:缓存型池子通常用于执行一些生存期很短的异步型任务,因此在一些面向连接的daemon型SERVER中用得不多。
  • 超时:能reuse的线程,必须是timeout IDLE内的池中线程,缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。
  • 结束:注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT不活动,其会自动被终止。

[java]  view plain  copy
 print ?
  1. // 线程池的大小会根据执行的任务数动态分配  
  2. ExecutorService cachedThreadPool = Executors.newCachedThreadPool();  
  3.   
  4. public static ExecutorService newCachedThreadPool() {  
  5.     return new ThreadPoolExecutor(0,                 //core pool size  
  6.                                   Integer.MAX_VALUE, //maximum pool size  
  7.                                   60L,               //keep alive time  
  8.                                   TimeUnit.SECONDS,  
  9.                                   new SynchronousQueue<Runnable>());  
  10. }  

执行结果:

[plain]  view plain  copy
 print ?
  1. 第1次任务的第1次执行  
  2. 第4次任务的第1次执行  
  3. 第3次任务的第1次执行  
  4. 第2次任务的第1次执行  
  5. 第3次任务的第2次执行  
  6. 第4次任务的第2次执行  
  7. 第2次任务的第2次执行  
  8. 第1次任务的第2次执行  
  9. 第2次任务的第3次执行  
  10. 第4次任务的第3次执行  
  11. 第3次任务的第3次执行  
  12. 第1次任务的第3次执行  
  13. 第2次任务的第4次执行  
  14. 第1次任务的第4次执行  
  15. 第3次任务的第4次执行  
  16. 第4次任务的第4次执行  

4个任务是交替执行的


FixedThreadPool

在FixedThreadPool中,有一个固定大小的池。

如果当前需要执行的任务超过池大小,那么多出的任务处于等待状态,直到有空闲下来的线程执行任务,

如果当前需要执行的任务小于池大小,空闲的线程也不会去销毁。


  • 重用:fixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程
  • 固定数目:其独特之处在于,任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子
  • 超时:和cacheThreadPool不同,FixedThreadPool没有IDLE机制(可能也有,但既然文档没提,肯定非常长,类似依赖上层的TCP或UDP IDLE机制之类的),
  • 使用场景:所以FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器
  • 源码分析:从方法的源代码看,cache池和fixed 池调用的是同一个底层池,只不过参数不同:
    fixed池线程数固定,并且是0秒IDLE(无IDLE)
    cache池线程数支持0-Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60秒IDLE

[java]  view plain  copy
 print ?
  1. // 创建可以容纳3个线程的线程池  
  2. ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);  
  3.   
  4. public static ExecutorService newFixedThreadPool(int nThreads) {  
  5.         return new ThreadPoolExecutor(nThreads, //core pool size  
  6.                                       nThreads, //maximum pool size  
  7.                                       0L,       //keep alive time  
  8.                                       TimeUnit.MILLISECONDS,  
  9.                                       new LinkedBlockingQueue<Runnable>());  
  10. }  

执行结果:
[plain]  view plain  copy
 print ?
  1. 第1次任务的第1次执行  
  2. 第3次任务的第1次执行  
  3. 第2次任务的第1次执行  
  4. 第3次任务的第2次执行  
  5. 第2次任务的第2次执行  
  6. 第1次任务的第2次执行  
  7. 第3次任务的第3次执行  
  8. 第1次任务的第3次执行  
  9. 第2次任务的第3次执行  
  10. 第3次任务的第4次执行  
  11. 第1次任务的第4次执行  
  12. 第2次任务的第4次执行  
  13. 第4次任务的第1次执行  
  14. 第4次任务的第2次执行  
  15. 第4次任务的第3次执行  
  16. 第4次任务的第4次执行  

创建了一个固定大小的线程池,容量为3,然后循环执行了4个任务。由输出结果可以看到, 前3个任务首先执行完,然后空闲下来的线程去执行第4个任务


SingleThreadExecutor 

SingleThreadExecutor得到的是一个单个的线程,这个线程会保证你的任务执行完成。

如果当前线程意外终止,会创建一个新线程继续执行任务,这和我们直接创建线程不同,也和newFixedThreadPool(1)不同。

[java]  view plain  copy
 print ?
  1. // 创建单个线程的线程池,如果当前线程在执行任务时突然中断,则会创建一个新的线程替代它继续执行任务    
  2. ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();  
  3.   
  4. public static ExecutorService newSingleThreadExecutor() {  
  5.         return new FinalizableDelegatedExecutorService  
  6.             (new ThreadPoolExecutor(1,  //core pool size  
  7.                                     1,  //maximum pool size  
  8.                                     0L, //keep alive time  
  9.                                     TimeUnit.MILLISECONDS,  
  10.                                     new LinkedBlockingQueue<Runnable>()));  
  11. }  

执行结果:

[plain]  view plain  copy
 print ?
  1. 第1次任务的第1次执行  
  2. 第1次任务的第2次执行  
  3. 第1次任务的第3次执行  
  4. 第1次任务的第4次执行  
  5. 第2次任务的第1次执行  
  6. 第2次任务的第2次执行  
  7. 第2次任务的第3次执行  
  8. 第2次任务的第4次执行  
  9. 第3次任务的第1次执行  
  10. 第3次任务的第2次执行  
  11. 第3次任务的第3次执行  
  12. 第3次任务的第4次执行  
  13. 第4次任务的第1次执行  
  14. 第4次任务的第2次执行  
  15. 第4次任务的第3次执行  
  16. 第4次任务的第4次执行  

4个任务是顺序执行的



ScheduledThreadPool

ScheduledThreadPool是一个固定大小的线程池,与FixedThreadPool类似,执行的任务是定时执行

[java]  view plain  copy
 print ?
  1. // 效果类似于Timer定时器  
  2. ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);  
  3.   
  4. public ScheduledThreadPoolExecutor(int corePoolSize) {  
  5.         super(corePoolSize,      //core pool size  
  6.               Integer.MAX_VALUE, //maximum pool size  
  7.               0,                 //keep alive time  
  8.               TimeUnit.NANOSECONDS,  
  9.               new DelayedWorkQueue());  
  10. }  

执行结果:

[plain]  view plain  copy
 print ?
  1. 第1次任务的第1次执行  
  2. 第2次任务的第1次执行  
  3. 第3次任务的第1次执行  
  4. 第2次任务的第2次执行  
  5. 第1次任务的第2次执行  
  6. 第3次任务的第2次执行  
  7. 第2次任务的第3次执行  
  8. 第1次任务的第3次执行  
  9. 第3次任务的第3次执行  
  10. 第2次任务的第4次执行  
  11. 第1次任务的第4次执行  
  12. 第3次任务的第4次执行  
  13. 第4次任务的第1次执行  
  14. 第4次任务的第2次执行  
  15. 第4次任务的第3次执行  
  16. 第4次任务的第4次执行  

——与FixedThreadPool的区别?


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值