Concurrent In Java 6 分享,第二部分 线程池

第一部分 集合 http://jimichan.iteye.com/blog/951948

 

第二部分 线程池 http://jimichan.iteye.com/blog/951950

 

第三部分 锁 http://jimichan.iteye.com/blog/951954

 

第四部分 同步辅助类 http://jimichan.iteye.com/blog/951955

Concurrent In Java,第二部分 线程池

 

2011-3-9  延昭 & 陈汝烨 版权所有,特别禁止发布到百度文库

这篇是来自公司内部分享会议是写的总结,有些内容没有表达出来,大家可以来踩,但是需留下原因,以便后续补充。

2. ThreadPool

虽然线程和进程相比是轻量级许多,但是线程的创建成本还是不可忽律,所以就有了线程池化的设计。线程池的创建、管理、回收、任务队列管理、任务分配等细节问题依然负责,没有必要重复发明轮子,concurrent包已经为我们准备了一些优秀线程池的实现。

 

2.1 认识ExecutorService 接口

 

ExecutorService 接口,它能提供的功能就是用来在将来某一个时刻异步地执行一系列任务。虽然简单一句话,但是包含了很多需求点。它的实现至少包含了线程池和任务队列两个方面,其实还包括了任务失败处理策略等。

经常使用submit方法,用来提交任务对象。

 

简单的例子:

 

                ExecutorService es = Executors.newCachedThreadPool();

                

                es.submit(new Runnable(){

                        @Override

                        public void run() {

                                System.out.println("do some thing");                

                        }

                });

 

                es.shutdown();

 

 

 

上面的例子只是完成了提交了一个任务,异步地去执行它。但是有些使用场景更为复杂,比如等待获得异步任务的返回结果,或者最多等上固定的时间。

 

submit 方法返回一个对象,Future。看起来有点别扭,代表将来的对象。其实看一下Future的方法就明白了。

 

 

 

 

 

 

 

 

其实Future对象代表了一个异步任务的结果,可以用来取消任务、查询任务状态,还有通过get方法获得异步任务返回的结果。当调用get方法的时候,当前线程被阻塞直到任务被处理完成或者出现异常。

 

我们可以通过保存Future对象来跟踪查询异步任务的执行情况。

 

显然Runnable接口中定义的 public void run();方法并不能返回结果对象,所以concurrent包提供了Callable接口,它可以被用来返回结果对象。

 

2.2 ThreadPoolExecutor

ThreadPoolExecutor实现了ExecutorService 接口,也是我们最主要使用的实现类。

 

首先非常有必要看一些类的最完整的构造函数

 

ThreadPoolExecutor(int corePoolSize,

   int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue<Runnable> workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler)

 

ThreadPoolExecutor对象中有个poolSize变量表示当前线程池中正在运行的线程数量。

 

注意:这个有关非常重要的关系,常常被误解。poolSize变量和corePoolSize、maximumPoolSize以及workQueue的关系。

 

首先线程池被创建初期,还没有执行任何任务的时候,poolSize等于0;

每次向线程池提交任务的时候,线程池处理过程如下:

 

1. 如果poolSize少于 corePoolSize,则首选添加新的线程,而不进行排队。

2. 如果poolSize等于或多于 corePoolSize,则首选将请求加入队列workQueue,而不添加新的线程。

3. 如果第二步执行失败(队已满),则创建新的线程执行任务,但是如果果poolSize已经达到maximumPoolSize,那么就拒绝该任务。如果处理被拒绝的任务就取决于RejectedExecutionHandler handler的设置了,默认情况下会抛出异常。

系统存在四种任务拒绝策略:

  1. 在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序遭到拒绝将抛出运行时 RejectedExecutionException
  2. 在 ThreadPoolExecutor.CallerRunsPolicy 中,线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
  3. 在 ThreadPoolExecutor.DiscardPolicy 中,不能执行的任务将被删除。
  4. 在 ThreadPoolExecutor.DiscardOldestPolicy 中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。

 

keepAliveTime活动线程如果空闲一段时间是否可以回收,通常只作用于超出corePoolSize的线程。corePoolSize的线程创建了就不会被回收。但是到java 6 之后增加了public void allowCoreThreadTimeOut(boolean value)法,允许core进程也可以根据keepAliveTime来回收,默认为false。

 

决定线程池特性的还有workQueue的实现类,有三种类SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue,分别对应同步队列、无界队列、有界队列。

 

  (摘自JavaDoc)

  1. 类SynchronousQueue,直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务(设置maximumPoolSizes 为Integer.MAX_VALUE)。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  2. LinkedBlockingQueue,无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  3. ArrayBlockingQueue,有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。

 

综上:构造参数的设置是互相制约和影响的。只有当你重复了解其相互关系的时候、或有特殊需求的时候,才可以自己构造ThreadPoolExecutor对象,否则可以使用Executores是个工厂类。

 

提示使用线程池是注意处理shutdown,确保你系统关闭的时候主动关闭shutdown。

2.3 ScheduledExecutorService

扩展了ExecutorService接口,提供时间排程的功能。

 

schedule(Callable<V> callable, long delay, TimeUnit unit)

         创建并执行在给定延迟后启用的 ScheduledFuture。

schedule(Runnable command, long delay, TimeUnit unit)

         创建并执行在给定延迟后启用的一次性操作。

scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnitunit)

         创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;也就是将在 initialDelay 后开始执行,然后在initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推。

scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit)

         创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。

 

 

 

schedule方法被用来延迟指定时间来执行某个指定任务。如果你需要周期性重复执行定时任务可以使用scheduleAtFixedRate或者scheduleWithFixedDelay方法,它们不同的是前者以固定频率执行,后者以相对固定频率执行。

 

(感谢wenbois2000 提出原先的错误,我在这里重新描述!对于原先的错误,实在不好意思啊,再次感谢!)

 

不管任务执行耗时是否大于间隔时间,scheduleAtFixedRate和scheduleWithFixedDelay都不会导致同一个任务并发地被执行。唯一不同的是scheduleWithFixedDelay是当前一个任务结束的时刻,开始结算间隔时间,如0秒开始执行第一次任务,任务耗时5秒,任务间隔时间3秒,那么第二次任务执行的时间是在第8秒开始。

 

ScheduledExecutorService的实现类,是ScheduledThreadPoolExecutor。ScheduledThreadPoolExecutor对象包含的线程数量是没有可伸缩性的,只会有固定数量的线程。不过你可以通过其构造函数来设定线程的优先级,来降低定时任务线程的系统占用。

 

 

特别提示:通过ScheduledExecutorService执行的周期任务,如果任务执行过程中抛出了异常,那么过ScheduledExecutorService就会停止执行任务,且也不会再周期地执行该任务了。所以你如果想保住任务都一直被周期执行,那么catch一切可能的异常。

2.4 Executors

Executores是个工厂类,用来生成ThreadPoolExecutor对象,它提供了一些常用的线程池配置方案,满足我们大部分场景。

 

1. newCachedThreadPool

public static ExecutorService newCachedThreadPool() {

       return new ThreadPoolExecutor(0, Integer.MAX_VALUE,

                                     60L, TimeUnit.SECONDS,

                                     new SynchronousQueue<Runnable>());

   }

 

分析下出这个线程池配置的工作模式,当没有空闲进程时就新建线程执行,当有空闲线程时就使用空闲线程执行。当线程空闲大60秒时,系统自动回收线程。

 

该线程池非常适合执行短小异步任务时吞吐量非常高,会重复利用CPU的能力。但是如果任务处理IO边界任务,那么会消耗大量线程切换,降低系统吞吐量。所以执行短小的计算任务非常高效,且当没有任务时不会消耗系统资源。

 

注意:线程池中没有变量表示线程是否空闲。那么程序是如何控制的呢?不得不赞叹concurrent实现的非常精巧。当创建出来的线程完成原来的任务后,会调用BlockingQueue的Poll方法,对于SynchronousQueue实现而言会阻塞调用线程,直到另外的线程offer调用。

 

然而ThreadPool在分配任务的时候总是先去尝试调用offer方法,所以就会触发空闲线程再次调用。

 

精妙的是ThreadPoolExecutor的处理逻辑一样,但是用BlockingQueue实现变了就产生不同的行为。

 

2. newFixedThreadPool

 

public static ExecutorService newFixedThreadPool(int nThreads) {

       return new ThreadPoolExecutor(nThreads, nThreads,

                                     0L, TimeUnit.MILLISECONDS,

                                     new LinkedBlockingQueue<Runnable>());

   }

 

创建固定线程数量的线程池,采用无界队列,当有更多任务的时候将被放入工作队列中排队。如果线程池不经常执行任务时,你可以调用allowCoreThreadTimeOut(boolean value)的方法让系统自动回收core的进程,以节约系统资源。

 

3. newSingleThreadExecutor

 

  public static ExecutorService newSingleThreadExecutor() {

       return new FinalizableDelegatedExecutorService

           (new ThreadPoolExecutor(1, 1,

                                   0L, TimeUnit.MILLISECONDS,

                                   new LinkedBlockingQueue<Runnable>()));

   }

只有一个工作线程的线程池。和newFixedThreadPool(1)相比,不同之处有两点:

1. 不可以重新配置newSingleThreadExecutor创建出来的线程池。

2. 当创建出来的线程池对象被GC回收时,会自动调用shutdown方法。

 

4.newScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {

       return new ScheduledThreadPoolExecutor(corePoolSize);

   }

生成一个可以执行时间调度的线程池。其实内部使用无界工作队列,线程数量最多能达到corePoolSize。

 

2.5 ExecutorCompletionService

 

这是个巧妙的设计,内部维护了一已经完成了任务结果队列,通过take方法可以同步地等待一个个结果对象。

详情见http://www.oschina.net/uploads/doc/javase-6-doc-api-zh_CN/java/util/concurrent/ExecutorCompletionService.html

 

 

第三部分 LOCK

http://jimichan.iteye.com/blog/951954

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值