浅谈ExecutorService线程池创建

转载至https://www.cnblogs.com/zedosu/p/6665306.html

ThreadPoolExecutor最核心的构造方法 

构造方法参数讲解 

参数名 作用
corePoolSize 核心线程池大小
maximumPoolSize 最大线程池大小
keepAliveTime 线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)使得核心线程有效时间
TimeUnit keepAliveTime时间单位
workQueue 阻塞任务队列
threadFactory 新建线程工厂
RejectedExecutionHandler 当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理

重点讲解: 
其中比较容易让人误解的是:corePoolSize,maximumPoolSize,workQueue之间关系。 

1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。 
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行 
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务 
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理 
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程 
6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

JDK提供了四种创建线程池

ExecutorService executor = Executors.newCachedThreadPool();
ExecutorService executor = Executors.newSingleThreadExecutor();
ExecutorService executor = Executors.newFixedThreadPool(3);
ExecutorService executor = Executors.newScheduledThreadPool(1);

1、缓冲功能的线程池,最大线程数为Integer的最大值。无容量的阻塞队列SynchronousQueue,当线程空闲超过60S后会自动关闭。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue());
}

2、固定线程数为1的线程池,保证由一个线程串行执行。无界LinkedBlockingQueue存放阻塞任务,因此多余的任务将存在再阻塞队列,不会由RejectedExecutionHandler处理 。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue()));
}

3、指定核心线程数与最大线程数一致的线程池,所以不会关闭线程。无界LinkedBlockingQueue存放阻塞任务,因此多余的任务将存在再阻塞队列。不会由RejectedExecutionHandler处理 

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue());
}

4、定义定时任务线程池,传入核心线程池参数。maximumPoolSize=Integer.MAX_VALUE,由于DelayedWorkQueue是无界队列,所以这个值是没有意义的

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

可以自己通过ThreadPoolExecutor构建线程池

 

ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("poolName-%d").build();

ExecutorService threadPool = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors()+1,
            Runtime.getRuntime().availableProcessors() + 1, 1L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue(256), threadFactory, 
            new ThreadPoolExecutor.AbortPolicy());

 

1、Runtime.getRuntime().availableProcessors()根据当前CPU核数进行设置,网上一般建议为CPU核数+1。

2、因为核心线程数和最大线程数一致,所以空闲线程的存活时间实际上可以不用设置。

3、等待队列的数量自定义。

4、可以通过设置ThreadFactory设置线程的名称。

5、线程池的拒绝策略有四种,根据业务需求自选也可以自己定义:

  • AbortPolicy:该策略是线程池的默认策略。使用该策略时,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
  • DiscardPolicy:这个策略和AbortPolicy的slient版本,如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常。
  • DiscardOldestPolicy:

    这个策略从字面上也很好理解,丢弃最老的。也就是说如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列。因为队列是队尾进,队头出,所以队头元素是最老的,因此每次都是移除对头元素后再尝试入队。

  • CallerRunsPolicy:使用此策略,如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行。就像是个急脾气的人,我等不到别人来做这件事就干脆自己干。

总结: 
1、用ThreadPoolExecutor自定义线程池,看线程是的用途,如果任务量不大,可以用无界队列,如果任务量非常大,要用有界队列,防止OOM 
2、如果任务量很大,还要求每个任务都处理成功,要对提交的任务进行阻塞提交,重写拒绝机制,改为阻塞提交。保证不抛弃一个任务 
3、最大线程数一般设为N+1最好,N是CPU核数 
4、核心线程数,看应用,如果是任务,一天跑一次,设置为0,合适,因为跑完就停掉了,如果是常用线程池,看任务量,是保留一个核心还是几个核心线程数 
5、如果要获取任务执行结果,用CompletionService,但是注意,获取任务的结果的要重新开一个线程获取,如果在主线程获取,就要等任务都提交后才获取,就会阻塞大量任务结果,队列过大OOM,所以最好异步开个线程获取结果

 

你可能感兴趣的:(并发编程)