线程池(Executors、Future、CountDownLatch、CyclicBarrier)

线程池

Executors、Future、CountDownLatch、CyclicBarrier

一、JDK创建

JDK5中提供的Executors工具类可以通过4个静态方法创建4种线程池

  1. 创建固定大小的线程池
    // 弊端:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量请求,从而导致OOM
    final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
    
  2. 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
    // 弊端:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量请求,从而导致OOM
    final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    
  3. 创建一个可缓存(无限大)的线程池,适合处理执行时间短的任务
    // 弊端:允许创建的线程数为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
    final ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    
  4. 创建可延时或定时执行的线程池
    // 弊端:允许创建的线程数为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
    final ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    
  5. 推荐下面方式
    /**
     * - 这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,
     * - 这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。
     * corePoolSize–池中要保留的线程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut
     * maximumPoolSize–池中允许的最大线程数,注意如果使用无界的阻塞队列,该参数没有什么效果。
     * keepAliveTime–当线程数大于核心时,这是多余空闲线程在终止前等待新任务的最长时间。
     * unit–keepAliveTime参数的时间单位,可选单位有DAYS、HOURS、MINUTES、毫秒、微秒、纳秒。
     * workQueue–用于在执行任务之前保留任务的队列。此队列将仅包含execute方法提交的Runnable任务。
     *      ArrayBlockingQueue: 基于数组结构的有界阻塞队列,按FIFO(先进先出)原则对任务进行排序。使用该队列,线程池中能创建的最大线程数为maximumPoolSize。
     *      LinkedBlockingQueue: 基于链表结构的无界阻塞队列,按FIFO(先进先出)原则对任务进行排序,吞吐量高于ArrayBlockingQueue。使用该队列,线程池中能创建的最大线程数为corePoolSize。静态工厂方法Executor.newFixedThreadPool()使用了这个队列。
     *      SynchronousQueue: 一个不存储元素的阻塞队列。添加任务的操作必须等到另一个线程的移除操作,否则添加操作一直处于阻塞状态。静态工厂方法Executor.newCachedThreadPool()使用了这个队列。
     *      PriorityBlokingQueue: 一个支持优先级的无界阻塞队列。使用该队列,线程池中能创建的最大线程数为corePoolSize。
     * threadFactory–执行器创建新线程时使用的工厂
     * handler–由于达到线程边界和队列容量而阻止执行时要使用的处理程序
     *      AbortPolicy: 无法处理新任务时,直接抛出异常,这是默认策略。
     *      CallerRunsPolicy:用调用者所在的线程来执行任务。
     *      DiscardOldestPolicy:丢弃阻塞队列中最靠前的一个任务,并执行当前任务。
     *      DiscardPolicy: 直接丢弃任务。
     */
     private static ThreadFactory threadFactory = new ThreadFactoryBuilder()
             .setNameFormat("demo-pool-%d").build();
     private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
             60L, TimeUnit.MILLISECONDS,
             new ArrayBlockingQueue<Runnable>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy());
    

二、Future的get()

Future接收线程执行结果;get()具有阻塞作用,等待执行结束

 public void m1() throws Exception {
     // 1、用于封装所有启动的异步线程
     List<Future<Object>> futureList = new ArrayList<>();

     // 2、
     IntStream.range(0, 5).forEach(v -> {
         final Future<Object> future = pool.submit(() -> {
             Thread.sleep((long) (Math.random()*5000));
             System.out.println(Thread.currentThread().getName());
             return true;
         });
         futureList.add(future);
     });

     // 4、使用Future的get()方法确定所有线程都执行完了
     for (Future<Object> future : futureList) {
         System.out.println(future.get()); // Future的get方法具有阻塞作用
     }

     // 5、TODO 其它业务逻辑

     System.out.println(LocalDateTime.now().format(formatter) + "==>结束");
 }

三、CountDownLatch减数计数器

CountDownLatch是一个倒数的计数器阀门,初始化时阀门关闭,指定计数的数量,当数量倒数减到0时阀门打开,被阻塞线程被唤醒。

 public void m2() throws Exception {
     System.out.println(LocalDateTime.now().format(formatter) + "==>开始");

     // 1、创建长度为10的计数器
     CountDownLatch latch = new CountDownLatch(10);

     // 2、启动10个线程
     IntStream.range(0, 10).forEach(v -> {
         pool.execute(() -> {
             try {
                 Thread.sleep((long) (Math.random()*5000));
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.printf("%s %s%n", LocalDateTime.now().format(formatter), Thread.currentThread().getName());
             // 减少锁存器的计数,如果计数为零,则释放所有等待的线程。
             latch.countDown();
         });
     });

     // 3、导致当前线程等待,直到锁存器倒计时到零,除非线程被中断
     latch.await();

     // 4、TODO 其它业务逻辑

     System.out.println(LocalDateTime.now().format(formatter) + "==>结束");
 }

四、CyclicBarrier循环栅栏

CyclicBarrier是一个可循环的屏障,它允许多个线程在执行完相应的操作后彼此等待共同到达一个point,等所有线程都到达后再继续执行(例如王者,需要等待所有人准备好才能开始)。
CyclicBarrier也可以像CountDownLatch一样适用于多个子任务并发执行,当所有子任务都执行完后再继续接下来的工作。

 public void m4() throws Exception {
 	System.out.println(LocalDateTime.now().format(formatter) + "==>开始");
 
 	final AbstractQueue<String> queue = new ArrayBlockingQueue<>(5);
 	queue.add("亚瑟");
 	queue.add("张飞");
 	queue.add("关羽");
 	queue.add("刘备");
 	queue.add("赵云");
 
 	// 1、创建长度为10的计数器
 	CyclicBarrier cyclic = new CyclicBarrier(5);
 
 	// 2、启动5个线程
 	IntStream.range(0, 5).forEach(v -> {
 		pool.execute(() -> {
 			String msg = String.format(" %s %s", Thread.currentThread().getName(), queue.poll());
 			try {
 				System.out.println(LocalDateTime.now().format(formatter) + msg + "进入游戏...");
 				Thread.sleep((long) (Math.random()*5000));
 				System.out.println(LocalDateTime.now().format(formatter) + msg + "准备就绪");
 				cyclic.await();
 			}catch (Exception e) {
 				e.printStackTrace();
 			}
 			System.out.println(LocalDateTime.now().format(formatter) + msg + "开始游戏...");
 			try {
 				Thread.sleep((long) (Math.random()*5000));
 				System.out.println(LocalDateTime.now().format(formatter) + msg + "结算完成");
 				cyclic.await();
 			}catch (Exception e) {
 				e.printStackTrace();
 			}
 			System.out.println(LocalDateTime.now().format(formatter) + msg + "退出游戏...");
 		});
 	});
 
 	// 4、TODO 其它业务逻辑
 
 	Thread.sleep(10000L);
 	System.out.println(LocalDateTime.now().format(formatter) + "==>结束");
 }

五、CyclicBarrier和CountDownLatch区别

  • CountDownLatch的await()线程会等待计数器减为0,而执行CyclicBarrier的await()方法会使线程进入阻塞等待其他线程到达障点。
  • CountDownLatch计数器不能重置,CyclicBarrier可以重置循环利用。
  • CountDownLatch是基于AQS的共享模式实现的,CyclicBarrier是基于ReentrantLock和Condition实现的。
  • CountDownLatch不会让子线程进入阻塞,CyclicBarrier会使所有子线程进入阻塞。

六、自定义线程池

  • 核心流程:
  1. 线程池中有N个工作线程
  2. 把任务提交给线程池运行
  3. 如果线程池已满,把任务放入队列
  4. 当有空闲线程时,获取队列中任务来执行
  5. 如果线程池和队列都满了,需要考虑拒绝策略
    • AbortPolicy :直接抛出异常,默认使用此策略
    • CallerRunsPolicy:用调用者所在的线程来执行任务
    • DiscardOldestPolicy:丢弃阻塞队列里最老的任务,也就是队列里靠前的任务
    • DiscardPolicy :当前任务直接丢弃
<think>我们参考用户提供的引用资料,用户希望不使用@Async注解,在Spring Boot中整合线程池CountDownLatch实现批量业务处理并返回处理结果。 引用[1]和[2]提到了@Async的使用和任务执行器的配置,但用户要求不使用@Async,因此我们需要手动创建和使用线程池。 引用[4]中有一个使用@Async注解并指定线程池的例子,同时使用了CountDownLatch来等待所有线程完成。我们可以借鉴这个CountDownLatch的用法,但去掉@Async注解,改为手动提交任务到线程池。 实现思路: 1. 在Spring Boot中配置一个线程池(可以使用ThreadPoolTaskExecutor)。 2. 将批量任务拆分成多个子任务,提交到线程池中执行。 3. 使用CountDownLatch来等待所有子任务完成。 4. 收集每个子任务的处理结果(可以使用Future或者自定义结果收集器)。 步骤: 步骤1:配置线程池 我们可以通过@Configuration和@Bean来定义一个线程池。 步骤2:编写任务执行方法 将需要异步执行的业务逻辑封装成Runnable或Callable任务。 步骤3:提交任务并管理 在需要批量处理的方法中,创建CountDownLatch,数量为任务数。然后循环提交任务到线程池,每个任务执行完后将countDown。同时,为了获取结果,我们可以使用Callable和Future。 步骤4:等待所有任务完成并收集结果 使用CountDownLatch.await()等待所有任务完成,然后从Future中取出结果。 示例代码: 1. 配置线程池(在配置类中): ```java @Configuration public class ThreadPoolConfig { @Bean("taskExecutor") public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数 executor.setCorePoolSize(10); // 最大线程数 executor.setMaxPoolSize(20); // 队列大小 executor.setQueueCapacity(200); // 线程名称前缀 executor.setThreadNamePrefix("task-"); // 拒绝策略 CallerRunsPolicy:当线程池没有处理能力的时候,该策略会直接在execute方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } } ``` 2. 定义一个服务类,用于执行批量任务: ```java @Service @Slf4j public class BatchProcessService { @Autowired @Qualifier("taskExecutor") private Executor taskExecutor; /** * 批量处理方法 * @param tasks 任务列表(每个任务是一个Callable,返回结果) * @return 每个任务的处理结果列表(按顺序) */ public <T> List<T> batchProcess(List<Callable<T>> tasks) throws InterruptedException { int taskCount = tasks.size(); // 创建CountDownLatch,用于等待所有任务完成 CountDownLatch countDownLatch = new CountDownLatch(taskCount); // 用于保存Future的列表 List<Future<T>> futures = new ArrayList<>(taskCount); // 用于保存结果的列表 List<T> results = new ArrayList<>(taskCount); // 遍历任务列表,提交到线程池 for (Callable<T> task : tasks) { // 将每个任务包装一下,使其在完成后调用countDownLatch.countDown() Callable<T> wrappedTask = () -> { try { return task.call(); } finally { countDownLatch.countDown(); } }; futures.add(taskExecutor.submit(wrappedTask)); } // 等待所有任务完成 countDownLatch.await(); // 获取每个任务的结果 for (Future<T> future : futures) { try { results.add(future.get()); } catch (ExecutionException e) { log.error("任务执行异常", e); // 根据需求,可以抛出异常或者设置默认值 // 这里我们抛出一个运行时异常 throw new RuntimeException(e); } } return results; } } ``` 3. 在业务中使用: 假设我们有一个批量插入用户的任务,我们可以这样调用: ```java @Service public class UserService { @Autowired private BatchProcessService batchProcessService; @Autowired private UserMapper userMapper; public List<Boolean> batchInsertUsers(List<User> userList) { // 将每个用户的插入操作封装成一个Callable任务 List<Callable<Boolean>> tasks = new ArrayList<>(); for (User user : userList) { tasks.add(() -> { try { // 这里执行插入操作,返回是否成功 int count = userMapper.insert(user); return count > 0; } catch (Exception e) { // 处理异常 return false; } }); } // 调用批量处理服务 try { return batchProcessService.batchProcess(tasks); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("批量插入用户被中断", e); } } } ``` 注意: 1. 上述BatchProcessService中,我们在每个任务执行完成后调用countDownLatch.countDown(),这样无论任务正常完成还是异常,都会计数减一。 2. 在获取结果时,我们按Future列表的顺序获取,注意这个顺序和任务提交顺序一致,但实际执行顺序可能不同。不过由于我们使用CountDownLatch等待所有任务完成,所以可以安全地获取结果。 3. 如果需要结果的顺序和提交顺序一致,那么使用Future列表按顺序获取即可。 另一种做法是不用CountDownLatch,而是通过遍历Future.get()来等待所有任务完成(因为Future.get()是阻塞的)。但是这样会按顺序等待,如果前面的任务执行时间长,即使后面的任务先完成也要等待前面的任务完成才能获取结果。而使用CountDownLatch可以同时等待所有任务完成,然后再按顺序获取结果,这样效率更高。 所以,我们使用CountDownLatch来同时等待所有任务完成,然后再获取结果。 用户要求返回处理结果,所以我们使用Callable来返回结果,而不是Runnable。 引用[4]中使用了CountDownLatch,并在finally中调用countDown(),我们也采用了类似的做法,确保每个任务无论成功失败都会调用countDown()。 综上,我们通过手动配置线程池,手动提交任务,结合CountDownLatchFuture,实现了不使用@Async注解的批量处理并返回结果的功能。 </think>在Spring Boot中不使用`@Async`注解实现线程池与`CountDownLatch`的整合,可通过以下步骤实现批量业务处理及结果返回: ### 实现步骤及代码示例 #### 1. 配置自定义线程池 ```java @Configuration public class ThreadPoolConfig { @Bean("customThreadPool") public ExecutorService customThreadPool() { return new ThreadPoolExecutor( 10, // 核心线程数 20, // 最大线程数 60L, TimeUnit.SECONDS, // 空闲线程存活时间 new LinkedBlockingQueue<>(100), // 任务队列 Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 ); } } ``` #### 2. 实现批量处理服务 ```java @Service public class BatchService { @Resource(name = "customThreadPool") private ExecutorService threadPool; /** * 批量处理方法 * @param dataList 待处理数据集合 * @return 包含每个任务处理结果的Map */ public Map<String, Object> processBatch(List<Object> dataList) { CountDownLatch latch = new CountDownLatch(dataList.size()); Map<String, Object> resultMap = new ConcurrentHashMap<>(); // 提交所有任务到线程池 for (Object data : dataList) { threadPool.submit(() -> { try { Object result = processSingle(data); // 业务处理 resultMap.put(data.getId().toString(), result); } catch (Exception e) { resultMap.put(data.getId().toString(), "处理失败: " + e.getMessage()); } finally { latch.countDown(); // 计数减一 } }); } // 等待所有任务完成 try { latch.await(30, TimeUnit.SECONDS); // 设置超时时间 } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("任务等待被中断", e); } return resultMap; } private Object processSingle(Object data) { // 单个业务处理逻辑 return "处理成功: " + data.hashCode(); } } ``` ### 关键设计说明 1. **线程池管理**: - 使用`ThreadPoolExecutor`显式配置线程池参数 - 通过`@Resource`注入自定义线程池实例 2. **任务调度机制**: ```mermaid graph LR A[主线程] --> B[初始化CountDownLatch] B --> C[提交任务到线程池] C --> D{任务处理} D -->|成功| E[保存结果] D -->|失败| F[记录异常] E & F --> G[计数减一] G --> H{所有任务完成?} H -->|是| I[返回聚合结果] H -->|超时| J[中断处理] ``` 3. **结果返回设计**: - 使用`ConcurrentHashMap`保证线程安全 - 通过`CountDownLatch.await()`实现主线程阻塞等待 - 设置超时时间防止死锁 4. **异常处理**: - 单个任务异常捕获不影响整体执行 - 主线程中断恢复处理(`Thread.currentThread().interrupt()`) ### 业务调用示例 ```java @RestController public class BatchController { @Autowired private BatchService batchService; @PostMapping("/batch-process") public ResponseEntity<Map<String, Object>> handleBatchRequest(@RequestBody List<Object> data) { Map<String, Object> results = batchService.processBatch(data); return ResponseEntity.ok(results); } } ``` ### 注意事项 1. **资源管理**: - 线程池需根据业务负载调整参数,避免OOM - 推荐使用`ThreadPoolTaskExecutor`替代原生`ThreadPoolExecutor`以获得Spring管理特性 2. **超时控制**: - `latch.await()`必须设置超时时间 - 超时后应主动中断线程(`threadPool.shutdownNow()`) 3. **结果一致性**: - 对于需要严格顺序的场景,可通过`Future`收集结果: ```java List<Future<?>> futures = new ArrayList<>(); futures.add(threadPool.submit(...)); for (Future<?> f : futures) { f.get(); // 按提交顺序获取结果 } ``` 此方案避免了`@Async`的AOP代理限制,特别适合需要精确控制线程生命周期的场景,如批量数据迁移、分布式计算等[^4]。 --- **相关问题:** 1. 如何防止线程池任务堆积导致内存溢出? 2. `CountDownLatch`和`CyclicBarrier`在批量处理中的区别? 3. 如何实现异步任务的结果回调机制? 4. Spring Boot中如何监控线程池运行状态?
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值