2

概要

线程池主要解决两个问题:当执行多个异步任务时,可以实验线程池来提高性能,因为使用线程池可以减少了每个任务的调用开销,并且提供了限制和管理资源的方式,例如线程资源。当线程池执行一个任务集合时,它也会持有一些基本的统计数据,例如完成任务的数量。为了能够在广泛的环境中可用,这个类提供了很多可调整的参数和一些可扩展的钩子。然而程序员都更加喜欢使用一些工厂方法:Executors#newCachedThreadPool(无界线程池并且线程可自动回收)、Executors#newFixedThreadPool(固定大小的线程池)、Executors#newSingleThreadExecutor(单个后台线程),这些工厂已经预配置的最常用的场景,你也可以手动的按照如下的指南配置和调整这个类:

核心(corePoolSize)和最大线程池数(maximumPoolSize):
ThreadPoolExecutor可以根据核心线程数和最大线程数自动调整池的大小。

当调用方法execute(Runable)提交一个新的任务时:

  • 如果正在运行的线程数小于corePoolSize:

    创建一个新的线程处理请求,即使其他的工作线程处于空闲状态。

  • 如果正在运行的线程数大于corePoolSize但是小于maximumPoolSize:

    只有队列满时才会重新创建一个新的线程。

通过设置corePoolSize=maximumPoolSize,你可以创建一个大小固定的线程池。

通过设置maximumPoolSize为一个无穷大数值(例如Integer.MAX_VALUE),那么说明你配置的线程池可以容纳任意数量的并发任务。

一般情况下,corePoolSize和maximumPoolSize都会在创建的时候指定的,但是你们也可以通过调用setCorePoolSize()setMaximumPoolSize()来动态的调整这两个值。

按需创建:
默认情况下,核心线程只有当任务到达时才会进行创建和开启,但是可以重写方法`prestartCoreThread`或者`prestartAllCoreThreads`改变这个行为。如果你构建的线程池带有一个非空的队列,你可能需要提前开启一些线程。
  • 创建新的线程:

    新的线程是通过使用ThreadFactory来创建的,如果没有指定的话,就会使用默认的Executors#defaultThreadFactory,这个默认的工厂创建出的线程都具有相同的ThreadGroup和相同的优先级并且都不是后台线程。通过实现一个不同的线程工厂,你可以修改线程的名字、线程组、优先级、后台状态等等。如果ThreadFactory在从newThread中返回null时未能创建线程,则执行程序将继续,但可能无法执行任何任务。线程应该具有修改线程的权限。如果工作线程或者其他线程使用线程池是不具有这个权限,服务可以会被降级:配置改变可能无法及时生效,and a shutdown pool may remain in a* state in which termination is possible but not completed。

Keep-alive时间:
如果线程池有超过corePoolSize数的线程数,如果这些过量的线程空闲时间超过`keepAliveTime`将会被终止。当线程池没有被使用充分时,这种机制可以降低资源的消耗。当线程池之后又变得活跃起来,新的线程又会被创建。这个参数可以被动态的改变,使用`setKeepAliveTime(long,TimeUnit)`。通过使用`Integer.MAX_VALUE`可以有效的禁用此功能。默认情况下,只有当前的线程数大于corePoolSize,这个策略才会生效。但是方法`allowCoreThreadTimeOut(boolean)`也能够将核心线程使用这种策略,只要`keepAliveTime`非0即可。
队列:
任何`BlockingQueue`都可以用来传输和保存提交的任务,此队列的使用和线程池大小有如下的交互:
    • 如果运行的线程数量小于corePoolSize:

      Executor会创建一个新的线程而不是添加到队列中。

    • 如果允许的线程数量大于corePoolSize:

      Executor会将任务添加到队列中而不是创建一个新的线程。

    • 如果一个请求不能添加到队列中(队列已满),如果允许的线程小于maximumPoolSize,将会创建一个新的线程,否则,该任务将会被拒绝。
    • 队列有三种常见的策略:

      • 直接提交: 它将任务直接提交给线程而不保存它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 SynchronousQueue线程安全的Queue,可以存放若干任务(但当前只允许有且只有一个任务在等待),其中每个插入操作必须等待另一个线程的对应移除操作,也就是说A任务进入队列,B任务必须等A任务被移除之后才能进入队列,否则执行异常策略。你来一个我扔一个,所以说SynchronousQueue没有任何内部容量。关于SynchronousQueue:http://blog.csdn.net/yanyan19...
      • 无界队列:使用一个没有提前预设容量的无界的队列,例如:LinkedBlockingQueue,当所有的核心线程处于繁忙时,所有新的任务都会被添加到队列中。因此如果运行线程数不超过corePoolSize,将会创建一个新的线程(maximumPoolSize这个值将不会再起作用),当每个任务完全独立于其他任务时,这可能是合适的,因此任务不能影响彼此的执行。在一个网页服务器。虽然这种排队方式可以有效地消除瞬时突发请求,但是当命令以比它们可以被处理的速度更快地平均到达时,可能会导致无限制的工作队列增长。
      • 有界队列:有限的队列(例如,ArrayBlockingQueue)有助于防止与有限的maximumPoolSizes一起使用时的资源耗尽,但可能更难以调整和控制。 队列大小和最大池大小可以相互交换:使用大队列和小池可以最大限度地减少CPU使用率,操作系统资源和上下文切换开销,但可能导致人为的低吞吐量。 如果任务经常阻塞(例如,如果它们是I / O型操作),则系统可能能够安排时间来获得比您允许的更多的线程。 使用小队列通常需要更大的池大小,这会使CPU更繁忙,但可能会遇到不可接受的调度开销,这也会降低吞吐量。
    拒绝任务:
    当Executor已经被关闭时,再调用`execute(Runable)`方法添加一个任务时将会被拒绝,当Executor使用有界队列时,队列和最大线程数都已经饱和,将会调RejectedExecutionHandler#rejectedExecution(Runnable, ThreadPoolExecutor)方法。提供4种预定义的处理器:
    

    ThreadPoolExecutor.AbortPolicy:该处理器抛出一个运行时异常RejectedExecutionException。

    ThreadPoolExecutor.CallerRunsPolicy:使用调用者自己的线程运行任务。 提供了一个简单的反馈控制机制,可以减慢提交新任务的速度。

    ThreadPoolExecutor.DiscardPolicy:丢弃任务。

    ThreadPoolExecutor.DiscardOldestPolicy:如果executor还没有被关闭,队列头部的任务将会被丢弃,并且重新执行(可能再一次失败,但是会重复执行)

    你可以定义并使用其他的实现自RejectedExecutionHandler的类。要做到这一点需要特别注意,特别是在策略仅在特定能力或排队政策下工作的情况下。

    钩子方法:
    这个类提供一写被protected修饰的方法:

    beforeExecute(Thread, Runnable),afterExecute(Runnable, Throwable)

    这些方法会在每个任务执行之前和执行之后被调用,这些可以用来操作执行环境;例如:重新初始化ThreadLocals,收集统计数据,或者添加log。另外,terminated也可以被重写当执行程序完全终止后需要执行的特殊处理。

    如果钩子或者callback方法抛出异常,内部工作线程可能会失败并突然终止。

    维护队列:
    方法`getQueue()`运行访问工作队列以此来进行监控和调试。强烈建议不要将这种方法用于任何其他目的。当大量的排队的任务被取消时,两个提供的方法`remove()`和`purge()`可用来帮助存储回收。
    
    回收:
    当线程池在程序中不在被引用并且不再持有线程,将会自动关闭。如果你希望确保即使用户忘记调用`shutdown()`也可以回收未引用的线程池,那么必须设置适当的保持活动的时间,使用0核心线程的下限来安排未使用的线程最终死亡或设置`allowCoreThreadTimeOut(boolean)`
    
    
    

    问题?

    • 队列中的三种策略中,直接提交的工作原理是怎么样的?
    • 如何扩展ThreadPoolExecutors?
    • 线程池的工作原理是怎么样的?如何提交一个任务?如何处理一个任务?
    • 创建一个线程的流程?

    取名很难
    119 声望9 粉丝

    « 上一篇
    FutureTask