一、使用Spring提供的并发线程池,只要在配置文件加入bean的配置即可。

因为应用是基于Spring的所以首先考虑使用Spring提供的并发线程池来做,但是这种方式程序运行期间很慢而且经常在开发环境将IDE搞得无响应,下面是org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor的配置:

<!-- Spring并发线程池 -->
     <bean id="threadPool"
         class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
         <!-- 核心线程数 -->
         <property name="corePoolSize" value="3" />
         <!-- 最大线程数 -->
         <property name="maxPoolSize" value="100" />
         <!-- 队列最大长度 >=mainExecutor.maxSize -->
         <property name="queueCapacity" value="200" />
         <!-- 线程池维护线程所允许的空闲时间 -->
         <property name="keepAliveSeconds" value="200" />
         <!-- 线程池对拒绝任务(无线程可用)的处理策略 ThreadPoolExecutor.CallerRunsPolicy策略 ,调用者的线程会执行该任务,如果执行器已关闭,则丢弃.  -->
         <property name="rejectedExecutionHandler">
             <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
         </property>
     </bean>


 

二、Spring 自带的线程池参数说明

Spring 自带的线程池 配置参数

< bean id=“taskExecutor”
 class=“org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor”>
 < !-- 核心线程数 -->
 < property name=“corePoolSize” value=“10” />
 < !-- 最大线程数 -->
 < property name=“maxPoolSize” value=“100” />
 < !-- 队列最大长度 >=mainExecutor.maxSize -->
 < property name=“queueCapacity” value=“1000” />
 < !-- 线程池维护线程所允许的空闲时间 -->
 < property name=“keepAliveSeconds” value=“300” />
 < !-- 线程池对拒绝任务(无线程可用)的处理策略 -->
 < property name=“rejectedExecutionHandler”>
 < bean class=“java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy” />
 </ property>
 </ bean>

配置的属性字段说明如下:
corePoolSize:线程池维护线程的最少数量
keepAliveSeconds:允许的空闲时间
maxPoolSize:线程池维护线程的最大数量
queueCapacity:缓存队列
rejectedExecutionHandler:对拒绝task的处理策略 

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务  如果任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务

 

线程池的参数运行原理:

如果此时线程池中的数量小于corePoolSize配置的值,即使线程池中的线程都处于空闲状态,也会创建新的线程来处理被添加的任务。

如果此时线程池中的数量等于 corePoolSize配置的值,但是缓冲队列 workQueue未满时,任务就会被放入缓冲队列中。

如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maxPoolSize,建新的线程来处理被添加的任务。

如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maxPoolSize,通过handler所指定的策略来处理此任务。
解释 处理任务的优先级为:
核心线程corePoolSize、
任务队列workQueue、
最大线程 maximumPoolSize,
如果以上三者都满了,
使用handler处理被拒绝的任务。

当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,线程池可以动态的调整池中的线程数。

 

 

四种线程池拒绝策略  

四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务 ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。。。。。
 

spring自带线程池使用不当导致的死锁问题

Spring自带线程池使用很方便,不过在相对复杂的并发编程场景中,使用时还是需要根据使用场景仔细考虑配置,否则可能会遇到本文中提及的坑。 具体的代码参照 示例项目 https://github.com/qihaiyan/springcamp/tree/master/spring-taskexecutor-block

一、概述

spring自带线程池有2个核心配置,一个是线程池的大小,一个是队列的大小。 ThredPoolTaskExcutor的处理流程: 新建线程并处理请求,直到线程数大小等于corePoolSize; 将请求放入workQueue中,线程池中的空闲线程去workQueue中取任务并处理; 当workQueue满时,就新建线程并处理请求,当线程池子大小大小等于maximumPoolSize时,会用RejectedExecutionHandler来做拒绝处理。

Reject策略有四种:

(1)AbortPolicy策略,是默认的策略,拒绝请求并抛出异常RejectedExecutionException。

(2)CallerRunsPolicy策略 ,由调用线程执行任务.

(3)DiscardPolicy策略,拒绝请求但不抛出异常.

(4)DiscardOldestPolicy策略,丢弃最早进入队列的任务.

二、多个异步处理共用同一个线程池的异常情况

模拟一个耗时的操作,该操作通过Async注解设置为异步执行。Async会默认使用名为taskExecutor的线程池。该操作返回一个CompletableFuture,后续的处理中会等待该异步操作执行完成。

@Service
public class DelayService {
    @Async
    public CompletableFuture<String> delayFoo(String v) {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(v + " runs in thread: " + Thread.currentThread().getName());
        return CompletableFuture.completedFuture(v);
    }
}

设置线程池,将线程池大小设置为2,队列设置为一个比线程池大的值,此处为10。当队列大小大于等于线程池大小时,就会出现本文遇到的程序阻塞的问题。

@Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(2);
        executor.setQueueCapacity(10);
        executor.setThreadNamePrefix("taskExecutor-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.initialize();
        return executor;
    }
}

并发处理:

while (true) {
        try {
            CompletableFuture.runAsync(
                () -> CompletableFuture.allOf(Stream.of("1", "2", "3")
                .map(v -> delayService.delayFoo(v))
                .toArray(CompletableFuture[]::new)) // 将数组中的任务提交到线程池中
                .join(), taskExecutor); // 通过join方法等待任务完成
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

三、问题分析

程序启动后,很快就会阻塞,通过jstack查看线程状态,发现taskExecutor-1、taskExecutor-2、main三个线程都处在WAITING状态,等待CompletableFuture.join方法执行完成。

priority:5 - threadId:0x00007f7f8eb36800 - nativeId:0x3e03 - nativeId (decimal):15875 - state:WAITING
stackTrace:
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000007961fe548> (a java.util.concurrent.CompletableFuture$Signaller)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.CompletableFuture$Signaller.block(CompletableFuture.java:1693)
at java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3323)
at java.util.concurrent.CompletableFuture.waitingGet(CompletableFuture.java:1729)
at java.util.concurrent.CompletableFuture.join(CompletableFuture.java:1934)

通过分析程序的执行过程,不难发现阻塞的原因。 由于线程池设置的Queue的大小大于线程池的大小,当线程池满时,delayFoo方法会处在队列中,随着程序的执行,总会出现线程池中都是CompletableFuture.join方法,队列中都是delayFoo方法的情况。

这时候线程中的join方法在等待队列中的delayFoo方法执行完成,而队列中的delayFoo方法由于等不到可用线程而执行,整个程序就陷入了死锁状态。

解决的方法也很简单,就是将队列的大小设置为小于线程数的大小,这样队列中的方法就有机会拿到线程,从而不会因为线程占满而进入死锁状态。