Java多线程  线程池

原文地址:Java多线程  线程池 作者:大关

本文由作者收集整理所得,作者不保证内容的正确行,转载请标明出处。

作者:关新全

Java多线程  线程池

2.1 ThreadPoolExecutor

         ThreadPoolExecutor类是ExecutorService的一个实现。一般使用线程池可以减少任务调用的开销,对执行大量异步任务时提供增强的性能,并能提供绑定和管理资源的方法。每个线程池还记录了一些线程统计相关的数据。一般很少直接使用ThreadPooleExecutor进行线程的直接配置,在Executors类中提供了很多特殊场合下,已经配置好的ThreadPoolExecutor类型的线程池,例如newCachedThreadPool将提供一个缓冲的线程池,newFixedThreadPool将提供一个指定大小的线程池等。

         下面主要探讨如何手动配置ThreadPoolExecutor

2.1.1 corePoolSizemaximumPoolSize

         ThreadPoolExecutor根据corePoolSizeMaximumPoolSize设置边界自动调整池的大小,如果在提交任务时,当前线程池运行任务的线程数目小于corePoolSize,即使已创建的线程有空闲的,ThreadPoolExecutor也会创建一个新的线程来执行新的任务。如果正在运行的线程数目在corePoolSizeMaximumPoolSize之间,除非队列已满,否则不会创建新的线程来执行新的任务。如果corePoolSizeMaximumPoolSize值相等,则此线程池是一个固定大小的线程池。可以将maximumPoolSize设置成大小无界(Integer.MAX_VALUE),则允许池适应任意大小的并发任务。可以通过ThreadPoolExecutor的构造函数或者ThreadPoolExecutor中提供的set方法设置corePoolSizemaximumPoolSize的大小。

2.1.2 创建新线程

         ThreadPoolExecutor使用ThreadFactory创建新线程。如果在构造ThreadPoolExecutor实例时提供了ThreadFactory,则通过提供的ThreadFactory构造新的线程,否则通过ExecutorsdefaultThreadFactory方法提供的ThreadFactory来创建新的线程。如果使用defaultThreadFactory提供的ThreadFactory来创建新线程,那么这些线程具有同样的优先级(NORM_PRIORITY)和守护进程状态等。可以通过自制的ThreadFactory改变线程名称、线程组、优先级、守护进程状态等。

2.1.3 keepAliveTime

         如果池中的线程数量多于corePoolSize,那么这些多余的线程将会在空闲时间超出keepAliveTime后被终止,释放相应的资源。这样可以使池在处于低活动状态时,减少资源消耗。可以在ThreadPoolExecutor的构造中提供keepAliveTime参数,或者通过设置setKeepAliveTime来动态设置此参数。默认情况下,keepAliveTime适用于非核心线程,使用allowCoreThreadTimeOut方法,可以设置将此策略也应用于核心线程。

2.1.4 排队

         ThreadPoolExecutor中使用BlockingQueue进行排队的。新任务与线程池间,可能出现下列交互。

1.       新任务到达时,线程池中活跃的线程数目小于corePoolSize,则添加新线程执行任务,而不考虑排队。

2.       新任务到达时,线程池中活跃的线程数目大于等于corePoolSize,则考虑将新任务放入队列。

3.       新任务到达时,线程池中活跃的线程数目大于corePoolSize,并且队列已经被填满,考虑创建一个新线程,执行任务。

4.       新任务到达时,线程池中活跃线程数等于maximumPoolSize,并且队列已经满了,则拒绝新任务。

         队列策略:

1.       直接提交。如果创建ThreadPoolExecutor时提供的是SynchronousQueue,它将任务直接提交给线程而不保存它们。如果不存在可用于立即运行任务的线程,则试图将新任务放入队列将会导致失败(就是队列满了,就是前面说的情况3),因此会构造一个新的线程。这种策略可以避免任务之间因为锁关系,造成的死锁等待问题,为了避免出现任务间死锁现象发生,应当尽量将线程池的maximumPoolSize设置为最大,以避免拒绝服务。(在Executors中的newCachedThreadPool使用这种策略)。

2.       无界队列。如果在创建ThreadPoolExecutor时提供的是像LinkedBlockingQueue这样的无界队列时,交互中的34两条根本不会发生。因此maximumPoolSize是一个多余的设置。也就是说线程池中的线程数不会超过corePoolSize。在线程任务互不影响,并且线程池需要有一定的增长性时,可以考虑使用无界队列。(web服务是一个典型的例子,因为在web服务中,每个任务都是相互独立的,不可能有锁和同步等问题)。

3.       有界队列。如果在创建ThreadPoolExecutor时传入的是一个设定大小的ArrayBlockingQueue(这只是其中一种实现),那么线程池可以防止由于过多的请求造成的资源耗尽。在使用有界队列过程中,需要对maximumPoolSize和队列大小进行一定的权衡。使用大队列小线程池,可以降低CPU的使用率、操作系统资源和上下文切换开销,但是可能降低了吞吐量,因为同一时间并发度太低。使用小队列大线程池,CPU使用率较高,但却可能带来不可接受的调度开销,因此也会降低吞吐量。

2.1.5 拒绝任务

         如果线程池已经关闭,或者线程池中队列已满并且活动线程到达了maximumPoolSize,此时新提交的任务将会被拒绝。拒绝处理程序将会调用RejectedExecutionHandler接口中的方法,对拒绝任务进行后续处理。ThreadPoolExecutor中提供了一些预定义的处理方法。

1.       默认使用ThreadPoolExecutor.AbortPolicy,新线程提交遭到拒绝,将会抛出RejectedExecutionException异常。

2.       使用ThreadPoolExecutor.CallerRunsPolicy,新线程将会被提交者线程执行(就是直接调用run方法,而不是创建一个新线程,调用start方法),这样很明显会减缓提交线程的提交速度。

3.       使用ThreadPoolExecutor.DiscardPolicy,新线程将会被直接抛弃,而没有任何反馈。

4.       使用ThreadPoolExecutor.DiscardOldestPolicy,将工作队列头部的任务删除,将新任务放入队列。

2.1.6 扩展(回调)

         可以重写ThreadPoolExecutor中的beforeExecuteafterExecute方法,以便在线程开启和关闭时插入统计信息,或者进行日志的记录。

2.1.7 终止

         对于不再引用的线程池,没有剩余的线程执行时,会自动执行shutdown(这个必须是0核心线程或者是allowCoreThreadTimeOut设置为true的情况)。可以使用shutdown方法的显示调用来确保池的回收,并取消正在执行的任务。建议显示使用关闭。

Graphic 2-1 ThreadPoolExecutor内部的类

[转载]Java多线程 <wbr> <wbr>线程池

 

Graphic 2-2 ThreadPoolExecutor公有操作

 

 [转载]Java多线程 <wbr> <wbr>线程池

2.2 Executors

         Executors类是一个ExecutorExecutorServiceScheduleExecutorServiceThreadFactory的工厂方法和实用方法。

         Graphic2-3给出了Executors的公共方法:

[转载]Java多线程 <wbr> <wbr>线程池

 

Graphic2-4 Executors内部结构

[转载]Java多线程 <wbr> <wbr>线程池



 

2.2.1 newCachedThreadPool

         Executors类的newCachedThreadPool方法,可以获得一个ExecutorService实例。通过newCachedThreadPool获得的线程池实例具有伸缩的特性,可以根据当前执行任务的多少动态的分配新的线程,在线程执行结束后,并不会别立即释放线程资源,而是等待其超过keepAliveTime后才会释放,如果在等待期间有新的任务到来,那么这个非活动线程将会接受新的任务。

         Demo1-1中可以看出,实际上newCachedThreadPool创建一个ThreadPoolExecutor实例,并且将corePoolSize设置为0,将maximumPoolSize设置为最大,keepAliveTime设置为60s,使用的是SynchronousQueue。当一个新任务到达时,由于corePoolSize0,所以无论何时线程池都试图向队列中插入元素,由于SynchronousQueue的特点,他不能接受任何类型的插入,因此,队列显示已经满了,因此会选择空闲的线程执行这个新任务,如果没有空闲线程,并且当前启动线程数又少于maximumPoolSize,则线程池选择创建新线程执行这个新任务。

         这种类型的线程池,适合提交多个线程之间有关联的情况,以防出现死锁,每次提交的线程任务都会被立即执行。

         还有一个与Demo1-1重载版本的newCachedThreadPool,带有一个ThreadFactory类型的参数。

Demo1-1给出了newCachedThreadPool的实现。

   public static ExecutorService newCachedThreadPool() {

        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,

                                      60L, TimeUnit.SECONDS,

                                      new SynchronousQueue<Runnable>());

    }

2.2.2 newFixedThreadPool

         Executors类的newFixedThreadPool方法,可以创建一个固定大小的线程池。这个线程池中一直维持着一个固定大小的线程数目(在开始执行时,这个线程数目有可能小于这个固定值)。当有新任务到达时,线程池先判断是否有空闲线程,如果有则使用空闲线程执行新任务,如果没有空闲线程,则将新任务放入对列。Demo1-2给出了newFixedThreadPool的实现。

         Demo1-2中可以看出,corePoolSizemaximumPoolSize设置成了相同的值,并且设置等待队列是一个无限阻塞队列。(这里的keepAliveTime设置是无意义的,因为线程数永远不可能超出corePoolSize)。当新任务到来时,线程池先查看是否有空闲的线程可以执行任务,如果存在这样的线程,则直接将任务提交给这个线程执行。如果没有空闲线程,则将任务放入队列中。(注意,即使maximumPoolSize设置大于corePoolSize,那么线程池也不会创建多于corePoolSize个线程,因为阻塞队列是一个无限的队列)。

Demo1-2 newFixedThreadPool的实现

   public static ExecutorService newFixedThreadPool(int nThreads) {

        return new ThreadPoolExecutor(nThreads, nThreads,

                                      0L, TimeUnit.MILLISECONDS,

                                      new LinkedBlockingQueue<Runnable>());

    }

2.3 ExecutorCompletionService

         CompletionService接口适合于执行几组任务时临时使用。Graphic2-3给出了ExecutorCompletionService类关系。CompletionServicetake方法,会阻塞等待这一组提交任务中的某一个执行结束就返回,并且将这个执行结束的线程结果反馈回来。可以想象可能存在这样一种情况,有一组计算,只要其中的一个计算结果执行结束,其他的计算结果就无关紧要时,使用CompletionService接口就方便的多,当我们将任务都提交给线程池,然后调用take方法阻塞等待其中一个任务执行结束,并反馈结果,其他的线程都可以取消。

         Demo2-3给出一个使用CompletionService的例子,在这个例子中,分别创建了两组CompletionService(即cs1cs2),cs1用来计算0-99的和,cs2用来计算0-999的和,这两个CompletionService都使用了同一个线程池。并且两组计算都是各自执行各自的,互不影响。

         使用CompletionService执行组任务会十分方便,在这个接口的实现类ExecutorCompletionService中,包装了一个Executor接口的实现,并且在创建ExecutorCompletionService时,可以为这个完成服务设置一个队列。这个队列是用来做完成队列来使用的。也就是说可以用这个队列取消那些未执行结束的线程。

         Graphic2-3类关系。

[转载]Java多线程 <wbr> <wbr>线程池

 

Demo1-3 使用ExecutorCompletionService例子。

package com.upc.upcgrid.guan.SpecialUse.chapter01;

 

import java.util.concurrent.Callable;

import java.util.concurrent.CompletionService;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.ExecutorCompletionService;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

 

public class CompletionExecutorInterface {

    private static class OnceComput implements Callable<Integer>{

       private int start;

       private int end;

      

       public OnceComput(int start, int end) {

           this.start = start;

           this.end = end;

       }

      

       @Override

       public Integer call() throws Exception {

           int sum = 0;

           for(int i = start ; i < end ; i++)

              sum += i;

           return sum;

       }

    }

 

    public static void main(String[] args) throws InterruptedException, ExecutionException {

       ExecutorService pool = Executors.newFixedThreadPool(5);

       CompletionService<Integer> cs1 = new ExecutorCompletionService<Integer>(pool);

       CompletionService<Integer> cs2 = new ExecutorCompletionService<Integer>(pool);

       for(int i = 0 ;i < 10 ; i++)

       {

           cs1.submit(new OnceComput(i*10, (i+1)*10));

       }

       for(int i = 0 ;i < 10 ;i++)

       {

           cs2.submit(new OnceComput(i*100,(i+1)*100));

       }

      

       int sum1 = 0;

       for(int i = 0 ; i < 10 ; i++)

       {

           sum1 += cs1.take().get();

       }

       System.err.println(sum1);

      

       int sum2 = 0;

       for(int i = 0 ; i < 10 ;i++)

       {

           sum2 += cs2.take().get();

       }

       System.err.println(sum2);

    }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值