`
jimichan
  • 浏览: 280992 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

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

    博客分类:
  • java
阅读更多

第一部分 集合 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

 

 

6
9
分享到:
评论
2 楼 jimichan 2011-03-12  
感谢您提出的问题,对我之前的理解错误表示抱歉,非常感谢你的动手实验
1 楼 wenbois2000 2011-03-12  
关于2.3节,scheduleAtFixedRate 方法不管上一次任务是否执行完毕,都会执行下一次的任务,如果周期为5秒钟,任务执行时间需要8秒钟,那么第5秒钟的时候系统会再次启动一个任务执行,此时系统存在两个任务实例同时运行。如果你的任务执行耗时不确定,而且希望你的任务需要one-by-one不重叠地进行,那么就可以选用者 scheduleWithFixedDelay方法。

我试验的结果是在使用scheduleAtFixedRate 时,任务在上次执行未完成是不会继续开始的,并且在ScheduledExecutorService.scheduleAtFixedRate的Javadoc 上也明确注释了这一点:
If any execution of this task takes longer than its period, then subsequent executions may start late, but will not concurrently execute.


下面是完整的JavaDoc
    /**
     * Creates and executes a periodic action that becomes enabled first
     * after the given initial delay, and subsequently with the given
     * period; that is executions will commence after
     * <tt>initialDelay</tt> then <tt>initialDelay+period</tt>, then
     * <tt>initialDelay + 2 * period</tt>, and so on.
     * If any execution of the task
     * encounters an exception, subsequent executions are suppressed.
     * Otherwise, the task will only terminate via cancellation or
     * termination of the executor.  If any execution of this task
     * takes longer than its period, then subsequent executions
     * may start late, but will not concurrently execute.
     *
     * @param command the task to execute
     * @param initialDelay the time to delay first execution
     * @param period the period between successive executions
     * @param unit the time unit of the initialDelay and period parameters
     * @return a ScheduledFuture representing pending completion of
     *         the task, and whose <tt>get()</tt> method will throw an
     *         exception upon cancellation
     * @throws RejectedExecutionException if the task cannot be
     *         scheduled for execution
     * @throws NullPointerException if command is null
     * @throws IllegalArgumentException if period less than or equal to zero
     */
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
						  long initialDelay,
						  long period,
						  TimeUnit unit);

相关推荐

    自定义实现Java线程池1-模拟jdk线程池执行流程1

    【自定义Java线程池实现】 在Java编程中,线程池是一种高效管理线程资源的方式,可以提高系统的性能和响应速度。...如《Effective Java》第二版第47条建议,避免重复造轮子,使用成熟且经过优化的类库。

    Concurrent Programming in Java

    《Concurrent Programming in Java》第二版是一本非常有价值的参考书,适合希望深入了解Java并发编程的开发人员。通过本书的学习,读者不仅可以掌握Java提供的强大并发工具,还能学会如何设计健壮、高效的并发程序。...

    Java并发编程:设计原则与模式(Concurrent.Programming.in.Java)(中英版)

    《Java并发编程:设计原则与模式》是一本深入探讨Java多线程编程的权威书籍,由Doug Lea撰写,第二版全面涵盖了Java并发处理的各个方面。这本书不仅提供了丰富的理论知识,还介绍了实战中的设计原则和模式,对于Java...

    Concurrent.Programming.in.Java - Design.Principles.and.Patterns(Second.Edition)

    # 并发编程在Java中的应用:设计原则与模式(第二版) ## 一、并发对象导向编程概览 ### 1.1 引言 本书《Concurrent Programming in Java - Design Principles and Patterns (Second Edition)》深入探讨了在Java...

    Concurrent_Programming+Java Concurrency in Practice+langspec

    最后,"Addison_Wesley_-_Concurrent_Programming_in_Java_2nd_Ed_(1999).pdf"是《并发编程在Java》的第二版,由Doug Lea撰写。这本书是Java并发编程领域的另一部里程碑作品,它在Java 1.4时代就深入介绍了线程、...

    1999+-+Concurrent+Programming+in+Java[1].+Design+Principles+and+Pattern+2nd-ed+-+Doug+Lea.rar

    《并发编程在Java》是Doug Lea的经典著作,第二版详细阐述了Java平台上的并发设计原则和模式。这本书深入探讨了如何有效地编写多线程和并发应用程序,是Java程序员理解和利用并发的重要参考资料。 并发编程是现代...

    Java线程池介绍Java开发Java经验技巧共8页.pd

    Java线程池是Java并发编程中的重要组成部分,它在多线程编程中扮演着至关重要的角色,有效地管理和调度线程资源,提高了程序的性能和稳定性。本文将深入探讨Java线程池的概念、工作原理以及如何在实际开发中运用。 ...

    java线程池源码-cThreadPool:JAVA线程池源码分析与重写

    项目描述:对java.util.concurrent包下线程池相关源码进行重新实现,深入研究和学习线程池超时机制、饱和策略、生命周期等知识 ThreadPoolExecutor类下部分方法和内部类介绍: 1、Worker类: 描述:Worker类实现...

    java常用工具类封装

    Java中的线程池是由`java.util.concurrent`包中的`ExecutorService`接口及其实现类如`ThreadPoolExecutor`提供的。线程池可以有效地管理和控制并发执行的任务数量,避免频繁创建和销毁线程带来的性能开销。通过设置...

    java的concurrent用法详解

    #### 二、`java.util.concurrent`包的关键概念与组件 ##### 2.1 Executor框架 `Executor`框架是`java.util.concurrent`的核心组件之一,它为任务的执行提供了一个统一的接口。其中最重要的接口是`ExecutorService`...

    concurrent 多线程 教材

    19 适用于 Java 程序员的 CSP ,第 2 部分.mht 20 适用于 Java 程序员的 CSP ,第 3 部分.mht 21 实现一个不受约束的不变性模型.mht 22 实时 Java,第 3 部分 线程化和同步.mht 23 IBM 的 Java 诊断,第 3 部分 ...

    Java concurrency线程池之线程池原理(四)_动力节点Java学院整理

    Java concurrency线程池之线程池原理 Java concurrency线程池是一种高效的线程池实现,用于管理和执行多个线程任务。线程池原理是Java concurrency线程池的核心机制,用于管理线程池中的线程资源。下面将详细介绍...

    关于 java.util.concurrent 您不知道的 5 件事,第 2 部分

    在Java编程领域,`java.util.concurrent`包是并发编程的核心工具包,提供了高效、线程安全的类和接口,使得开发者能够更容易地处理多线程环境。本篇将深入探讨这个包中一些鲜为人知的知识点,以帮助你提升并发编程的...

    JavaThreaddemo_DEMO_tidecme_线程池Java_源码.zip

    Java线程池是Java并发编程中的重要组成部分,它在多线程编程中扮演着至关重要的角色,有效地管理和调度线程资源,提高系统性能并降低资源消耗。本资料"JavaThreaddemo_DEMO_tidecme_线程池Java_源码.zip"包含了关于...

    线程池示例代码

    在Java中,`java.util.concurrent`包提供了`ExecutorService`接口和它的实现类,如`ThreadPoolExecutor`,用于创建线程池。`ThreadPoolExecutor`构造函数接收几个关键参数:核心线程数、最大线程数、存活时间、时间...

    实战Java高并发程序设计第二版随书代码

    《实战Java高并发程序设计》第二版是一本深入探讨Java多线程和并发编程的书籍。这本书涵盖了Java并发编程的核心概念和技术,旨在帮助开发者在实际项目中高效地处理高并发场景。随书附带的代码提供了丰富的示例,以便...

    Java API(两部分中的第二部分)

    Java 1.6增强了并发编程的支持,提供了`java.util.concurrent`包,包含线程池、Future、Callable接口以及并发集合类,如ConcurrentHashMap、CopyOnWriteArrayList等,这些使得多线程编程更加高效和安全。...

    android 线程池下载

    在Android中,我们可以使用`java.util.concurrent`包下的`ExecutorService`和`ThreadPoolExecutor`来创建线程池。`ExecutorService`是线程池的接口,而`ThreadPoolExecutor`是它的具体实现,提供了更灵活的配置选项...

    并发容器和线程池,java并发编程3

    在Java中,为了提高程序的并发处理能力,Java标准库提供了多个线程安全的并发容器,它们主要位于`java.util.concurrent`包中。这些容器能够有效地管理共享资源的访问,并确保在多线程环境下数据的一致性和完整性。 ...

Global site tag (gtag.js) - Google Analytics