Java 并发控制:防止重复批次执行的设计与实现

Java 并发控制:防止重复批次执行的设计与实现

问题分析

需求拆解

  1. 避免重复推送:确保相同批次任务不会被多次提交。
  2. 单批次串行:同一时间只有一个实例在执行重复批次。
  3. 多批次并行:不同批次任务可以并行处理。

关键点

唯一性控制 :需要一个线程安全的机制来记录当前正在处理的批次。
并发控制 :需要确保对批次状态的操作是线程安全的。
任务调度 :线程池负责任务的并发执行。


解决方案

我们可以使用 ConcurrentHashMap 来存储批次的执行状态,并结合 Future 或自定义锁机制来控制任务的执行。以下是具体实现步骤:

  1. 使用 ConcurrentHashMap 管理批次状态
    使用 ConcurrentHashMap<String, Boolean> 来记录批次的状态,其中键是批次 ID,值表示该批次是否正在执行。
    当一个批次被推送时,先检查 ConcurrentHashMap 中是否存在该批次。如果不存在,则允许执行;如果存在,则跳过。
  2. 使用 ReentrantLock 或 synchronized 控制并发
    为了确保对批次状态的修改是原子操作,可以使用 ReentrantLock 或 synchronized 来避免竞争条件。
  3. 线程池调度任务
    使用 ExecutorService 提交任务到线程池中执行。
    在任务完成后,从 ConcurrentHashMap 中移除该批次的状态。

代码实现

import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;

public class BatchProcessor {
    // 用于存储批次状态的线程安全 Map
    private final ConcurrentHashMap<String, Boolean> batchStatusMap = new ConcurrentHashMap<>();
    private final ExecutorService executorService;
    private final ReentrantLock lock = new ReentrantLock();

    public BatchProcessor(int poolSize) {
        this.executorService = Executors.newFixedThreadPool(poolSize);
    }

    /**
     * 提交批次任务
     *
     * @param batchId 批次 ID
     * @param task    批次任务
     */
    public void submitBatch(String batchId, Runnable task) {
        // 尝试获取锁以保证线程安全
        lock.lock();
        try {
            // 检查批次是否已经在处理中
            if (batchStatusMap.putIfAbsent(batchId, true) == null) {
                // 如果批次不存在,则提交任务到线程池
                executorService.submit(() -> {
                    try {
                        task.run(); // 执行任务
                    } finally {
                        // 任务完成后,移除批次状态
                        batchStatusMap.remove(batchId);
                    }
                });
            } else {
                System.out.println("批次 " + batchId + " 已经在处理中,跳过重复推送");
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * 关闭线程池
     */
    public void shutdown() {
        executorService.shutdown();
    }

    public static void main(String[] args) {
        BatchProcessor processor = new BatchProcessor(5);

        // 模拟推送批次任务
        for (int i = 0; i < 10; i++) {
            String batchId = "batch-" + (i % 3); // 模拟重复批次
            processor.submitBatch(batchId, () -> {
                System.out.println("处理批次 " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // 模拟任务耗时
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        // 关闭线程池
        processor.shutdown();
    }
}

代码解析

ConcurrentHashMap 的作用

  • putIfAbsent 方法用于检查批次是否已经存在,如果不存在则插入新批次并返回 null,否则返回已存在的值。
    这种方式保证了批次状态的唯一性。

ReentrantLock 的作用

  • 虽然 ConcurrentHashMap 是线程安全的,但在某些情况下(如复杂的业务逻辑)可能需要额外的锁来确保操作的原子性。

任务的生命周期管理

  • 在任务完成后,通过 finally 块确保从 ConcurrentHashMap 中移除批次状态,避免内存泄漏。

线程池的作用

  • 使用固定大小的线程池来限制并发任务的数量,避免资源耗尽。

运行结果示例

假设我们推送了 10 个批次任务,其中批次 ID 为 “batch-0”、“batch-1” 和 “batch-2” 循环出现,程序输出可能如下:

处理批次 pool-1-thread-1
处理批次 pool-1-thread-2
处理批次 pool-1-thread-3
批次 batch-0 已经在处理中,跳过重复推送
批次 batch-1 已经在处理中,跳过重复推送
批次 batch-2 已经在处理中,跳过重复推送
...

优化方向

批量处理 :

  • 如果批次任务较多,可以考虑批量提交任务以减少锁的竞争。

动态线程池 :

  • 根据任务量动态调整线程池大小,避免资源浪费或不足。

持久化批次状态 :

  • 如果需要跨 JVM 或重启后保持批次状态,可以将批次状态存储在数据库或分布式缓存中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值