java 线程池 详解

引言

合理利用线程池能够带来三个好处:

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

 

本篇参考:http://www.infoq.com/cn/articles/java-threadPool

                  http://www.cjsdn.net/Doc/JDK60/

 

关于线程池的配置参数

Java中创建线程池创建如下:

Java代码   收藏代码
  1. ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);  

 ThreadPoolExecutor的构造函数有多个,通过构造函数的参数配置线程池,以下是各个参数的说明:

corePoolSize:线程池的基本大小,maximumPoolSize:线程池最大大小,即线程池允许创建的最大线程数,线程池会根据corePoolSize(调用getCorePoolSize())和maximumPoolSize(调用getMaximumPoolSize())去自动调整池中的线程数量(调用getPoolSize())。当池中的线程数少于corePoolSize的时候,会创建新的线程放到任务队列处理请求,不管队列中其他的线程是否空闲;当线程数大于coolPoolSize而小于maximumPoolSize的时候,如果池中任务队列为空,则创建线程放入任务队列。当设置maximumPoolSize为Integer.MAX_VALUE,则这个线程池为无界线程池,然后池中的线程数量是任意的。如果任务队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务,该线程等待进入任务队列。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。(鄙人估计corePoolSize确定了任务队列的大小)

 

workQueue:任务队列,用于转移和阻塞提交了的任务,即任务队列是运行线程的,任务队列根据corePoolSize和maximumPoolSize工作:

   1.当正在运行的线程小于coolPoolSize,线程池会创建新的线程。

   2.当大于coolPoolSize而任务队列未满,则从队列里拿一个空闲的线程去接任务。

   3.当大于coolPoolSize而任务队列满了(即队列中没有空闲的线程),并且小于maximumPoolSize,会创建新的线程接任务,该线程等待进入任务队列。

   4.当大于maximumPoolSize,该任务会根据handler(RejectedExecutionHandler,饱和策略)处理。

参考图:

 任务队列又有以下集中策略模式:

    1.直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。    

    2.无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

  3. 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。

 

handler:RejectedExecutionHandler(饱和策略),当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。

   1.在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序遭到拒绝将抛出运行时 RejectedExecutionException

   2.在 ThreadPoolExecutor.CallerRunsPolicy 中,线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。

   3.在 ThreadPoolExecutor.DiscardPolicy 中,不能执行的任务将被删除。

   4.在 ThreadPoolExecutor.DiscardOldestPolicy 中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。

定义和使用其他种类的  RejectedExecutionHandler 类也是可能的,但这样做需要非常小心,尤其是当策略仅用于特定容量或排队策略时。

keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。
unit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
threadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。如果没有另外说明,则在同一个  ThreadGroup 中一律使用  Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的  NORM_PRIORITY 优先级和非守护进程状态。通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。如果从  newThread 返回 null 时  ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务。
 
钩子 (hook) 方法 
此类提供  protected 可重写的  beforeExecute(java.lang.Thread, java.lang.Runnable) 和  afterExecute(java.lang.Runnable, java.lang.Throwable) 方法,这两种方法分别在执行每个任务之前和之后调用。它们可用于操纵执行环境;例如,重新初始化 ThreadLocal、搜集统计信息或添加日志条目。此外,还可以重写方法  terminated() 来执行 Executor 完全终止后需要完成的所有特殊处理。   如果钩子 (hook) 或回调方法抛出异常,则内部辅助线程将依次失败并突然终止。队列维护   方法  getQueue() 允许出于监控和调试目的而访问工作队列。强烈反对出于其他任何目的而使用此方法。 remove(java.lang.Runnable) 和  purge() 这两种方法可用于在取消大量已排队任务时帮助进行存储回收。
 
线程池的关闭
我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池,它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别,shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。
 
总结
举个不太优雅的例子,将线程池比喻成一个窑子,每个任务都是嫖客,线程就是接待客人的妹子,一个任务一条线程(不搞多P),corePoolSize是坐台的妹子数,maximumPoolSize是妹子的总数,任务队列是排着正在ooxx的以及准备ooxx的其大小为一次最多只允许有多少对在ooxx,keepAliveTime是每个妹子最大工作时长,做完若没超过时长就是闲着的,从闲着的坐台妹子选出再接另外的,若没闲着的坐台妹子,再叫人数过来补充,但不能超过妹子总数,超过了的话按经营策略( RejectedExecutionHandler——策略 处理, 所以可以想象,窑子老板就想多赚,坐台的妹子只要还行,接着来,不会轻易给你个新鲜的 因为有任务队列大小限制(房间有限,允许ooxx的对数不能太多),若队列满了,生意好,客人只能携着妹子等了。。。。大概意思是如此,给个图自己脑补一下:

 
 
以下是示例代码:
ThreadPoolGenerator.java,封装了ThreadPoolExecutor的创建:
Java代码   收藏代码
  1. package testThreadPool;  
  2.   
  3. import java.util.concurrent.ArrayBlockingQueue;  
  4. import java.util.concurrent.BlockingQueue;  
  5. import java.util.concurrent.Executors;  
  6. import java.util.concurrent.RejectedExecutionHandler;  
  7. import java.util.concurrent.ThreadFactory;  
  8. import java.util.concurrent.ThreadPoolExecutor;  
  9. import java.util.concurrent.TimeUnit;  
  10.   
  11. public class ThreadPoolGenerator {  
  12.   
  13.     private volatile static ThreadPoolExecutor threadPoolExecutor;  
  14.       
  15.     //写了自己的一个Executor,主要是用于线程池执行完所有任务所需的时间,重写了terminated方法  
  16.     public static class MyThreadPoolExecutor extends ThreadPoolExecutor {  
  17.   
  18.         long sTime = 0L;  
  19.         long eTime = 0L;  
  20.           
  21.         public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize,  
  22.                 long keepAliveTime, TimeUnit unit,  
  23.                 BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,  
  24.                 RejectedExecutionHandler handler) {  
  25.             super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,  
  26.                     threadFactory, handler);  
  27.         }  
  28.           
  29.         @Override  
  30.         protected void terminated() {  
  31.             super.terminated();  
  32.             if(sTime != 0l) {  
  33.                 eTime = System.currentTimeMillis();  
  34.                 System.out.println("executor completed tasks take: " + (eTime - sTime) + " ms");  
  35.             }  
  36.               
  37.         }  
  38.           
  39.         //记录Executor开始执行的那刻的时间  
  40.         public void startLogTime() {  
  41.             sTime = System.currentTimeMillis();  
  42.         }  
  43.     }  
  44.       
  45.     public static ThreadPoolExecutor getThreadPoolExecutor(int corePoolSize, int maximumPoolSize,  
  46.             Long keepAliveTime, int quequeSize, ThreadFactory threadFactory, RejectedExecutionHandler handler) {  
  47.           
  48.         if(threadPoolExecutor == null) {  
  49.             synchronized (ThreadPoolGenerator.class) {  
  50.                 if(threadPoolExecutor == null) {  
  51.                     threadFactory = threadFactory == null ? Executors.defaultThreadFactory() : threadFactory;  
  52.                     handler = handler == null ? new ThreadPoolExecutor.AbortPolicy() : handler;  
  53.                       
  54.                     threadPoolExecutor = new MyThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,  
  55.                             TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(quequeSize), threadFactory, handler);  
  56.                 }  
  57.             }  
  58.         }  
  59.           
  60.         return threadPoolExecutor;  
  61.     }  
  62. }  
TestTask.java, 一个测试任务类:
Java代码   收藏代码
  1. package testThreadPool;  
  2.   
  3. public class TestTask implements Runnable{  
  4.   
  5.     private String name;  
  6.       
  7.     public TestTask(String name) {  
  8.         this.name = name;  
  9.     }  
  10.       
  11.       
  12.     //模拟任务,此处两个for 循环,若循环次数不大,那么有线程执行跟没线程执行效果差不多的  
  13.     public void performTask() {  
  14.         for(int i=0; i<100000000; i++){  
  15.             for(int j=0; j<1000000; j++) {  
  16.                 j=i+j;  
  17.             }  
  18.         }  
  19.           
  20.         System.out.println(name + " completed.");  
  21.     }  
  22.   
  23.     //通过线程执行模拟任务  
  24.     @Override  
  25.     public void run() {  
  26.         this.performTask();  
  27.           
  28.     }  
  29. }  
 
TestCase.java,测试类:
Java代码   收藏代码
  1. package testThreadPool;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5. import java.util.concurrent.ThreadPoolExecutor;  
  6.   
  7. import testThreadPool.ThreadPoolGenerator.MyThreadPoolExecutor;  
  8.   
  9.   
  10. public class TestCase {  
  11.   
  12.       
  13.     public static void main(String[] args) {  
  14.           
  15.         List<TestTask> tasks = new ArrayList<TestTask>();  
  16.         for(int i=1; i<=5; i++)   
  17.             tasks.add(new TestTask("Task " + i));  
  18.           
  19.           
  20.         TestCase c = new TestCase();  
  21.           
  22.         System.out.println("no thread case:");  
  23.           
  24.         long sTime = System.currentTimeMillis();  
  25.           
  26.         c.noThreadProcess(tasks);  
  27.           
  28.         long eTime = System.currentTimeMillis();  
  29.         long tTime = eTime - sTime;  
  30.         System.out.println("process take :" + tTime + "ms");  
  31.           
  32.         System.out.println("=========================");  
  33.   
  34.           
  35.         System.out.println();  
  36.           
  37.           
  38.         System.out.println("with thread case:");  
  39.           
  40.         ThreadPoolExecutor executor = ThreadPoolGenerator.getThreadPoolExecutor(35, 5000L, 3nullnull);  
  41.           
  42.         c.withThreadProcess(executor, tasks);  
  43.           
  44.         eTime = System.currentTimeMillis();  
  45.           
  46.         System.out.println("=========================");  
  47.           
  48.     }  
  49.       
  50.       
  51.       
  52.     public void withThreadProcess(ThreadPoolExecutor executor, List<TestTask> list) {  
  53.           
  54.         //此举用来启动记录执行耗时  
  55.         ((MyThreadPoolExecutor)executor).startLogTime();  
  56.           
  57.         for(TestTask task : list) {  
  58.             executor.execute(task);  
  59.         }  
  60.         executor.shutdown();  
  61.           
  62.     }  
  63.       
  64.     public void noThreadProcess(List<TestTask> list) {  
  65.           
  66.         for(TestTask task : list) {  
  67.             task.performTask();  
  68.         }  
  69.           
  70.     }  
  71.   
  72. }  
 测试结果:
Sql代码   收藏代码
  1. no thread case:  
  2. Task 1 completed.  
  3. Task 2 completed.  
  4. Task 3 completed.  
  5. Task 4 completed.  
  6. Task 5 completed.  
  7. process take :2802ms  
  8. =========================  
  9.   
  10. with thread case:  
  11. =========================  
  12. Task 3 completed.  
  13. Task 2 completed.  
  14. Task 1 completed.  
  15. Task 4 completed.  
  16. Task 5 completed.  
  17. executor completed tasks take: 1224 ms  
 
 
 线程池另外一种用法,用线程池执行任务,完成后的取得执行结果:
TestMessageTask.java,那个badTask如果为true,表示为坏任务,模拟任务执行出错:
Java代码   收藏代码
  1. package testThreadPool;  
  2.   
  3. import java.util.concurrent.Callable;  
  4.   
  5. public class TestMessageTask implements Callable<String> {  
  6.   
  7.     private String name;  
  8.     private boolean badTask;  
  9.       
  10.     public TestMessageTask(String name, boolean badTask) {  
  11.         this.name = name;  
  12.         this.badTask = badTask;  
  13.     }  
  14.       
  15.     @Override  
  16.     public String call() throws Exception {  
  17.         for(int i=0; i<100000000; i++){  
  18.             for(int j=0; j<1000000; j++) {  
  19.                 j=i+j;  
  20.             }  
  21.         }  
  22.           
  23.         //用了一个boolean,模拟执行出错的情形  
  24.         if(badTask) {  
  25.             throw new RuntimeException(name + " is bad task!!");  
  26.         }  
  27.         return name + " completed.";  
  28.     }  
  29.   
  30. }  
 
MessageThreadPoolExecutor.java:
Java代码   收藏代码
  1. package testThreadPool;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5. import java.util.concurrent.BlockingQueue;  
  6. import java.util.concurrent.Callable;  
  7. import java.util.concurrent.CancellationException;  
  8. import java.util.concurrent.ExecutionException;  
  9. import java.util.concurrent.Future;  
  10. import java.util.concurrent.FutureTask;  
  11. import java.util.concurrent.RejectedExecutionHandler;  
  12. import java.util.concurrent.RunnableFuture;  
  13. import java.util.concurrent.ThreadFactory;  
  14. import java.util.concurrent.ThreadPoolExecutor;  
  15. import java.util.concurrent.TimeUnit;  
  16.   
  17. public class MessageThreadPoolExecutor extends ThreadPoolExecutor{  
  18.   
  19.       
  20.     //把父类的构造函数全弄出来算了。。。  
  21.     public MessageThreadPoolExecutor(int corePoolSize, int maximumPoolSize,  
  22.             long keepAliveTime, TimeUnit unit,  
  23.             BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {  
  24.         super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);  
  25.     }  
  26.   
  27.     public MessageThreadPoolExecutor(int corePoolSize, int maximumPoolSize,  
  28.             long keepAliveTime, TimeUnit unit,  
  29.             BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {  
  30.         super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,  
  31.                 threadFactory);  
  32.     }  
  33.   
  34.     public MessageThreadPoolExecutor(int corePoolSize, int maximumPoolSize,  
  35.             long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {  
  36.         super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);  
  37.     }  
  38.   
  39.     public MessageThreadPoolExecutor(int corePoolSize, int maximumPoolSize,  
  40.             long keepAliveTime, TimeUnit unit,  
  41.             BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,  
  42.             RejectedExecutionHandler handler) {  
  43.           
  44.         super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,  
  45.                 threadFactory, handler);  
  46.     }  
  47.       
  48.       
  49.     //定义了一个方法执行任务集合,用了泛型,表示结果类型由Callable类型的任务对象决定  
  50.     public <T> Map<String, T> performTasks(Map<String, Callable<T>> taskMap) throws InterruptedException{  
  51.           
  52.         if(taskMap == null || taskMap.isEmpty())   
  53.             throw new NullPointerException();  
  54.           
  55.         Map<String, Future<T>> futureMap = new HashMap<String, Future<T>>();  
  56.         Map<String, T> messageMap = new HashMap<String, T>();  
  57.           
  58.         boolean done = false;  
  59.           
  60.         try {  
  61.               
  62.             for(String key : taskMap.keySet()) {  
  63.                 /** 
  64.                  * 这里用了两种方式执行任务:execute与submit,建议翻翻API文档, 
  65.                  * 关于Future的get方法,没弄懂。。。 
  66.                  */  
  67.                 //execute 方式  
  68.                 /*RunnableFuture<T> futureTask = new FutureTask<T>(taskMap.get(key)); 
  69.                 execute(futureTask); 
  70.                 futureMap.put(key, futureTask);*/  
  71.                   
  72.                   
  73.                 //submit 方式  
  74.                 futureMap.put(key, submit(taskMap.get(key)));  
  75.                   
  76.             }  
  77.               
  78.             /** 
  79.              *  再次遍历任务,逐个调用get方法,get方法会阻塞住直到任务完成, 
  80.              *  get方法返回一个结果,根据结果判断任务执行成功与否,这也是我没有看懂 
  81.              *  API的地方,那个submit方法明明说返回的Future对象如果成功它的get方法 
  82.              *  返回null,但messageMap中的value是有的,不为null。。。  
  83.              */  
  84.             for(String key : futureMap.keySet()) {  
  85.                 Future<T> f = futureMap.get(key);  
  86.                 try {  
  87.                     T result = f.get();  
  88.                     messageMap.put(key, result);  
  89.                 } catch (ExecutionException e) {  
  90.                     System.out.println(e.getMessage());  
  91.                 }  
  92.             }  
  93.               
  94.             done = true;  
  95.             return messageMap;  
  96.         }finally {  
  97.             //若上面出了异常没done,没做完的任务直接cancel  
  98.             if(!done) {  
  99.                 for(String key : futureMap.keySet()) {  
  100.                     futureMap.get(key).cancel(true);  
  101.                 }  
  102.             }  
  103.               
  104.             this.shutdown();  
  105.         }  
  106.           
  107.     }  
  108.       
  109.   
  110. }  
 
测试代码,MsgTestCase.java:
Java代码   收藏代码
  1. package testThreadPool;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5. import java.util.concurrent.ArrayBlockingQueue;  
  6. import java.util.concurrent.Callable;  
  7. import java.util.concurrent.TimeUnit;  
  8.   
  9. public class MsgTestCase {  
  10.   
  11.     public static void main(String[] args) {  
  12.           
  13.         Map<String, Callable<String>>taskMap = new HashMap<String, Callable<String>>();  
  14.         for(int i=1; i<=5; i++) {  
  15.               
  16.             //随机产生坏任务  
  17.             int r = (int)(Math.random()*5 + 1);  
  18.             boolean badTask = (i==r);  
  19.               
  20.             taskMap.put("Task: " + i ,new TestMessageTask("Task: " + i, badTask));  
  21.         }  
  22.           
  23.           
  24.         MessageThreadPoolExecutor executor = new MessageThreadPoolExecutor(35, 3000L,   
  25.                     TimeUnit.MILLISECONDS,   
  26.                     new ArrayBlockingQueue<Runnable>(2));  
  27.           
  28.         try {  
  29.             Map<String, String> resultMap = executor.performTasks(taskMap);  
  30.               
  31.             for(String key : resultMap.keySet()) {  
  32.                 System.out.println(resultMap.get(key));  
  33.             }  
  34.         } catch (InterruptedException e) {  
  35.             e.printStackTrace();  
  36.         }   
  37.           
  38.     }  
  39.   
  40. }  
 测试结果:
Sql代码   收藏代码
  1. java.lang.RuntimeException: Task: 5 is bad task!!  
  2. java.lang.RuntimeException: Task: 2 is bad task!!  
  3. Task: 3 completed.  
  4. Task: 4 completed.  
  5. Task: 1 completed.  
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值