关于java线程池<三>

2013/06/13

这篇文章主要是翻译了一部分jdk自带线程池的API。本来不想翻译的,结果发现上周看完以后,过了个端午,玩了三天回来发现有些细节又忘记了。。想来想去,决定还是翻译一遍,英语水平有限。。剩下的你懂的。。

我是这么计划的,从上层接口开始一层一层往下。开始工作:

先发一张这篇打算翻译的接口及类的定义的层级关系




1.java.util.concurrent.Executor


java.util.concurrent.Executor,一个用于执行Runnable任务的实例接口。这个接口提供了一种将每个任务的提交与每个任务具体如何执行解耦的方式,这其中包括线程的使用与调度的细节等等。一个Executor通常被用于替代哪些显示的创建线程的方法。举个栗子,与其说显示的调用new Thread(new RunnableTask() ).start() 去执行一个堆任务,你还不如入像下面这么做:

 Executor executor = anExecutor;
 executor.execute(new RunnableTask1());
 executor.execute(new RunnableTask2());
 ...

上面这么写,每次执行execute方法就创建一个新线程,但是,Executor接口并没有严格的要求任务的执行必须是异步的。举个简单的栗子,一个executor也能让一个提交上来的任务直接在调用executor.execute方法的线程上执行例如:

 class DirectExecutor implements Executor {
     public void execute(Runnable r) {
         r.run();
     }
 }

但是,更普遍的写法是让任务执行在调用executor.execute方法的线程之外的线程上。下面的这个executor就为每一个任务创建一个新线程:

 class ThreadPerTaskExecutor implements Executor {
     public void execute(Runnable r) {
         new Thread(r).start();
     }
 }
许多Executor的实现类会附加一些限制类似于如何或者何时调用任务。下面的这个executor展示了一个复合型的executor,其将任务排队并移交给第二个executor执行:


 class SerialExecutor implements Executor {
     final Queue<Runnable> tasks = new ArrayDeque<Runnable>();
     final Executor executor;
     Runnable active;

     SerialExecutor(Executor executor) {
         this.executor = executor;
     }

     public synchronized void execute(final Runnable r) {
         tasks.offer(new Runnable() {
             public void run() {
                 try {
                     r.run();
                 } finally {
                     scheduleNext();
                 }
             }
         });
         if (active == null) {
             scheduleNext();
         }
     }

     protected synchronized void scheduleNext() {
         if ((active = tasks.poll()) != null) {
             executor.execute(active);
         }
     }
 }


这个Executor接口的实现为这个包下的ExecutorService,其是一个更加拓展的接口。ThreadPoolExecutor类提供了可扩展的线程池的实现。Executors类为上面说的这些Executors提供了便利的工厂方法。

内存一致性影响:一个调用了Executor.execute方法的线程,其中除了调用Executor.execute方法之外的操作应当那优先于这个runnable对象的执行,这个执行可能处于别的线程中。



2.java.util.concurrent.ExecutorService


ExecutorService是一个Executor,提供了用于关闭自己的方法,还有可以返回Future对象以追寻一个或多个任务的工作流程的方法。


一个ExecutorService是可以被关闭的,这样会导致拒绝任何新的任务。ExecutorService定义了两个不同的方法去关闭自己。一个是shutdown()方法,这个方法允许在调用这个方法之前提交上来的任务执行完毕,随后关闭。另外一个shutdownNow()方法则阻止还在等待的任务启动并且试图去停止正在执行的任务。在ExecutorService关闭的基础上,一个executor将没有可以执行的的任务,没有等待中的任务,没有新的提交上来的任务。一个不使用的ExecutorService应当被关闭,从而允许回收它所占用的资源。


submit()方法拓展了Executor.execute方法,创建并返回了一个Future对象,这个对象可以被用于取消执行或者等待任务完成状态。方法invokeAny和invokeAll提供了最经常使用到的大部分模板代码,如执行一系列的任务并且等待其中一个或者所有的任务完成(如果要重写上诉方法,推荐继承类 ExecutorCompletionService 并重写自定义的上述方法)


Executors类提供了ExecutorService所在包下 ExecutorService 类及子类的工厂方法。


使用示例:

这里是一个有一个线程池用于服务发送来的请求的网络服务的简单设计,这里使用Executors.newFixedThreadPool工厂方法来生成固定个数的线程池:

 class NetworkService implements Runnable {
   private final ServerSocket serverSocket;
   private final ExecutorService pool;

   public NetworkService(int port, int poolSize)
       throws IOException {
     serverSocket = new ServerSocket(port);
     pool = Executors.newFixedThreadPool(poolSize);
   }

   public void run() { // run the service
     try {
       for (;;) {
         pool.execute(new Handler(serverSocket.accept()));
       }
     } catch (IOException ex) {
       pool.shutdown();
     }
   }
 }

 class Handler implements Runnable {
   private final Socket socket;
   Handler(Socket socket) { this.socket = socket; }
   public void run() {
     // read and service request on socket
   }
 }

下面的方法分两阶段关闭一个ExecutorService,第一步,调用shutdow方法拒绝提交上来的任务;第二步,如果有必要,则调用shutdowNow方法,取消任何延迟的任务:

 void shutdownAndAwaitTermination(ExecutorService pool) {
   pool.shutdown(); // Disable new tasks from being submitted
   try {
     // Wait a while for existing tasks to terminate
     if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
       pool.shutdownNow(); // Cancel currently executing tasks
       // Wait a while for tasks to respond to being cancelled
       if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
     }
   } catch (InterruptedException ie) {
     // (Re-)Cancel if current thread also interrupted
     pool.shutdownNow();
     // Preserve interrupt status
     Thread.currentThread().interrupt();
   }
 }

内存一致性影响:一个线程中调用ExecutorService.submit方法之外的任何操作应当优先于这个被提交任务被执行,这些被提交任务的执行应当优先于通过Future.get()返回结果。


3.java.util.concurrent.AbstractExecutorService


提供ExecutorService的默认实现。这个类实现了submit,invokeAny和invokeAll方法,这些方法通过调用newTaskFor方法得到RunnableFuture对象,指向一个FutureTask引用。例如,submit(Runnable)方法就返回一个通过执行newTaskFor方法得到的RunnableFuture对象。子类可以重写newTaskFor方法,返回其他实现了RunnableFuture接口的实例。


扩展举例:一下是一个自定义的ThreadPoolExecutor的概要设计,使用了CustomTask类型替代默认的FutureTask:

 public class CustomThreadPoolExecutor extends ThreadPoolExecutor {

   static class CustomTask<V> implements RunnableFuture<V> {...}

   protected <V> RunnableFuture<V> newTaskFor(Callable<V> c) {
       return new CustomTask<V>(c);
   }
   protected <V> RunnableFuture<V> newTaskFor(Runnable r, V v) {
       return new CustomTask<V>(r, v);
   }
   // ... add constructors, etc.
 }


4.java.util.concurrent.ThreadPoolExecutor


ThreadPoolExecutor是一个ExecutorService,它通过使用一个或者有若干个保存在池中的线程执行提交上来的任务,通过Executors的工厂方法可以创建一般配置的ThreadPoolExecutor。


线程池旨在解决两个问题:1.线程池一般来说在处理大量异步任务时能够有更好的表现,原因是它减少了每个任务平均的执行开销。2.它提供了一种限制和管理资源的手段,其中包括它所管理的线程,以及它所管理的任务队列。每一个ThreadPoolExecutor都包含了基本的统计信息,例如这个线程池完成的任务数等等。


ThreadPoolExecutor提供了高可配的上下文环境,包括大量的可调整的参数以及遍历的钩子方法,使得ThreadPoolExecutor能更加通用。尽管如此,我们还是建议尽量使用Executors中的便利工厂方法:Executors.newCachedThreadPool(一个无界的线程池,配合自动线程回收),Executors.newFixedThreadPool(固定大小的线程池),以及Executors.newSingleThreadExecutor(独立线程线程池),这些预定义的线程池可以适用于大部分的使用场景。如果这些不适用的话,可以使用下面的指南进行人工配置并返回你想要的ThreadPoolExecutor:


核心及最大池大小:

一个ThreadPoolExecutor会根据coolPoolSize和maximumPoolSize的设置自动的调整线程池大小。当有一个新任务被提交(ThreadPoolExecutor.execute),且这一时刻少于corePoolSize数量的线程正在运行,那么一个新的线程将会被创建,并且用于处理这个新任务,即使当时存在其他空闲线程。如果这一时刻有超过coolPoolSize数量但是少于maximumPoolSize数量的线程正在运行,仅当任务队列充满的状态时才会创建新线程。通过设置corePoolSize和maximumPoolSize为同一个值,你可以创建一个固定大小的线程池。通过设置maximumPoolSize为一个本质上来说无限的值,例如Integer.MAX_VALUE,你允许这个池容纳任意数量的并发线程。大部分情况下,corePoolSize和maximumPoolSize都仅在构造方法中设定,但是你也可以通过ThreadPoolExecutor.setCorePoolSize和ThreadPoolExecutor.setMaximumPoolSize方法来动态设置。


按需构造方法:

默认的构造方法中,即使是核心线程也是在有新任务到达时进行初始化创建并启动,但是这件事情可以通过重写构造并调用ThreadPoolExecutor.restartCoreThead方法或者ThreadPoolExecutor.prestartAllCoreThreads方法从而在构造方法方法中启动核心线程。因为你可能想要启动一部分线程,介于你的任务队列在初始化的时候就不是空的。


创建新线程:

新的线程通过java.util.concurrent.ThreadFactory创建。如果没有特别声明,一个Executors.defaultThreadFactory对象将会被使用。这个对象创建出的线程都属于一个TrheadGroup,有着相同的优先级,并且都是非守护进程。通过提供不同的ThreadFactory,你可以改变Thread的命名方式,所属的ThreadGroup,优先级以及是否是守护进程,等等。如果一个ThreadFactory调用newThread方法创建线程失败,返回一个null,调用ThreadFactory的Executor依然继续运行,只是可能不能够处理任何任务。


存活时间:

如果一个线程池当前有超过corePoolSize的线程数,额外的线程当其空闲时间超过keepAliveTime就会被终止。这样就提供了当线程池并不是很活跃的时候,可以尽量减少资源消耗的手段。如果这个线程池之后又变得活跃起来,新的线程将会被创建并入池。keepAliveTime这个参数也可以同过ThreadPoolExecutor.setKeepAliveTime方法动态修改。通过使用Long.MAX_VALUE(TimeUnit.NANOSECONDS)能够有效的阻止空闲线程被终止。默认情况下,终止空闲线程的策略只会被应用到超出corePoolSize数的线程上,当时调用ThreadPoolExecutor.allowCoreThreadTimeOut(boolean)方法可以使这一超时策略同样作用于核心线程上,前提是keepAliveTime的值非0.


任务队列:

任意的BlockingQueue队列都有可能被用于传输和存储提交的任务。这个队列的调用是和线程池的大小息息相关的:

如果少于corePoolSize数量的线程正在运行,那么Executor总是选择添加一个新线程而不是将任务入队列。

如果多余corePoolSize数量的线程正在运行,那么Executor总是选择将任务入队列而不是增加一个新线程。

如果任务队列已满,且线程池未满,则Executor会选择创建新的线程。若任务队列和线程池都已饱和,则拒绝请求。


下面有三个基本的排队策略:

1.直接交付。一个不错的默认选择是同步队列,这种队列直接交付任务给Thread而从不存储任务。这样,一个因为没有空闲线程而试图入队列的任务将会不能进入队列,取而代之的是创建一个新的线程来处理任务。这种策略避免了当多个任务之间存在相互依赖时产生死锁的情况。直接交付策略一般来说要求你有一个无界的线程池从而保证不会拒绝新提交的任务。但是话说回来,当任务的到达平均速度高于任务的平均处理熟读时,这种策略也有可能存在无线的线程增长的弊端。


2.无界队列。通过使用无界队列(例如 LinkedBlockingQueue ,不设置预定义大小,这是它就是个无界队列) ,当所有核心线程都在工作时,新入队列的任务就讲等待。这样不会有超过corePoolSize的线程被创建(这样maximumPoolSize这个值就理所当然的失效了)。这种策略可能比较适合于每一个任务都是完全独立的情况,这种情况下每一个任务的执行都不会影响另一个任务的执行 。举个栗子,一个网页服务器,使用无界队列策略就能够平滑的处理短暂爆发的请求高峰。但是,也必须承认当任务的到达平均速度高于任务的平均处理速度时,将会出现无限任务队列不断增长的情况。


3.有界队列。一个有界的队列(例如一个ArrayBlockingQueue)通过设置maximumPoolSize可以帮助我们防止资源耗竭的情况,但是这种策略比前两种更加难以协调和控制。队列的大小和线程池的大小需要相互协调:使用大的任务队列和小的线程池可以最小化CPU使用率,OS资源,以及环境变化开销,但是可能导致人为的低吞吐量。如果任务频繁的阻塞(例如I/O阻塞),就应当腾出时间来提高你允许的最高线程数。使用小的任务队列一般来说要求大的线程池,这样能够充分利用CPU,但是也可能遇到不可预料的调度开销,同样会降低吞吐量。


拒绝任务:

通过ThreadPoolExecutor.execute提交的新任务,当Executor已经关闭,或者Executor使用有界的工作队列和线程池且已经饱和,这个任务将会被拒绝。在上面说的任意一个情况中,Executor的execute方法将会自己的RejectedExecutionHandler的rejectedExecution方法。4个预定义的处理策略如下所示:

1.默认的ThreadPoolExecutor.AbortPolicy:handler将会抛出一个运行时RejectExecutionExecption。

2.ThreadPoolExecutor.CallerRunsPolicy:调用Executor.execute的线程将会自己运行这个任务。这个策略提供一个简单的反馈控制机制从而降低新任务的提交速率。

3.ThreadPoolExecutor.DiscardPolicy:简单的抛弃不能执行的任务。

4.ThreadPoolExecutor.DiscardOldestPolicy:如果Executor并没有关闭,而是因为饱和而拒绝任务,那么在任务队列头的任务将会被抛弃,然后重新尝试执行当前任务(如果仍然失败,则重复这一策略)

可以定义并使用其他类型的RejectedExecutionHandler 类。这么做的话要求需要额外的小心,特别是这个策略被设计用于指定线程池大小或者任务队列大小的的线程池。


钩子方法:

ThreadPoolExecutor 提供了protected修饰的可重写的方法ThreadPoolExecutor.beforeExecute 和 ThreadPoolExecutor.afterExecute方法分别在执行任务前后调用。这两个方法可以被用于构造执行环境;例如,重新初始化ThreadLocal变量,收集统计信息,或者增加日志实体。值得一说的是,ThreadPoolExecutor.terminated方法也可以被重写用于在一个Executor将要彻底终止时,执行一些特别的,必须被完成的流程。

如果 钩子方法或者回调方法抛出异常,线程池内部的正在工作的线程会返回失败或者突然终止。


队列维护:

方法ThreadPoolExecutor.getQueue 允许以监视和调试的目的访问工作队列。以其他任何目的调用此方法都是非常不提倡的。这里提供两个方法:ThreadPoolExecutor.remove和ThreadPoolExecutor.purge。当大量的等待任务被取消时可以使用这两个方法辅助内存回收。


终止方法:

一个线程池最终没有任何程序引用且池中没有任何线程的时候,将会自动被关闭。如果你想要确认这些未被引用的线程池即使不手工调用ThreadPoolExecutor.shutdown也会被回收,你可以尝试这样做:设置合适的keepAliveTime,设置核心线程数为0或者设置ThreadPoolExecutor.allowCoreThreadTimeOut(true),通过以上方法可以使线程池中的线程全部最终死亡。


扩展栗子:

大多数ThreadPoolExecutor的子类都会重写一个或者多个钩子方法。例如,这里是一个子类增加了一个简单的暂停/恢复 特性:


 class PausableThreadPoolExecutor extends ThreadPoolExecutor {
   private boolean isPaused;
   private ReentrantLock pauseLock = new ReentrantLock();
   private Condition unpaused = pauseLock.newCondition();

   public PausableThreadPoolExecutor(...) { super(...); }

   protected void beforeExecute(Thread t, Runnable r) {
     super.beforeExecute(t, r);
     pauseLock.lock();
     try {
       while (isPaused) unpaused.await();
     } catch (InterruptedException ie) {
       t.interrupt();
     } finally {
       pauseLock.unlock();
     }
   }

   public void pause() {
     pauseLock.lock();
     try {
       isPaused = true;
     } finally {
       pauseLock.unlock();
     }
   }

   public void resume() {
     pauseLock.lock();
     try {
       isPaused = false;
       unpaused.signalAll();
     } finally {
       pauseLock.unlock();
     }
   }
 }
 

这个栗子中使用到了jdk1.5中的新的并发特性Lock,Condition用以替代Object的wait()和notify()方法。


终于翻译完了-  -
















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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值