java并发 定时读取和清空ConcurrentMap如何保证线程安全方案?

文章目录

❓实际场景

假设我们有一个场景, 它短时高发, 也就是会一瞬间出现很多数据, 这些数据需要持久化到数据库, 但是时效性又不高, 那么该如何设计呢?

思考中...

我们可以定义一个公共静态的ConcurrentMap,现将这些数据存放在map里面, 然后每隔一段时间将缓存数据批量写入数据库, 并同时将其清空。

注意点: 虽然ConcurrentMap是线程安全的,但是读取并清空这个操作需要原子性,即不能有其他线程在清空过程中插入数据导致数据丢失或重复处理。

❌无效方案:使用synchronized上锁

第一时间容易想到的是使用synchronized上锁, 但它其实是一个无效方案

示例代码:

复制代码
public class CacheManager {
    private static final ConcurrentMap<String, Object> cacheMap = new ConcurrentHashMap<>();
    private static final Object lock = new Object();

    // 定时任务调用的方法
    public static Map<String, Object> getAndClear() {
        synchronized (lock) {
            Map<String, Object> oldMap = new HashMap<>(cacheMap);
            cacheMap.clear();
            return oldMap;
        }
    }
}

问题随之而来, 其他操作cacheMap的方法也需要加锁吗?

答案是肯定的, 如果其他方法(如put、get)也需要保证在getAndClear时不被同时执行,那么它们也需要加锁。

但是这样会降低并发性能, 因此我们需要仔细考虑是否真的需要完全互斥。

💡方案对比

方案 原理 优点 缺点 适用场景
原子替换 使用 AtomicReference原子替换整个 Map 简单高效,无锁竞争 替换瞬间可能丢失少量数据 高并发,允许微小数据丢失
分段处理 将 Map 分段,分批处理 减少锁竞争,平滑处理 实现复杂,需要维护分段 大数据量,要求平滑处理
双缓冲 使用两个 Map 交替工作 零等待,高性能 内存占用翻倍 极高并发,不能有等待
事务快照 基于版本号或时间戳 精确控制,可回溯 实现复杂,存储开销 需要精确数据一致性

方案一:原子替换(推荐)

1.1 核心实现
复制代码
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.Map;

public class AtomicMapManager {
    // 使用 AtomicReference 包装 ConcurrentMap
    private static final AtomicReference<ConcurrentMap<String, Object>> mapRef = 
        new AtomicReference<>(new ConcurrentHashMap<>());
    
    /**
     * 定时任务:原子性地获取并清空Map
     */
    public void scheduledClearAndProcess() {
        // 原子替换:获取当前Map并替换为空Map
        ConcurrentMap<String, Object> currentMap = 
            mapRef.getAndSet(new ConcurrentHashMap<>());
        
        // 处理获取到的数据(此时其他线程写入新Map,不影响当前处理)
        processMapData(currentMap);
    }
    
    /**
     * 业务写入方法(线程安全)
     */
    public void putData(String key, Object value) {
        // 直接操作当前Map引用
        mapRef.get().put(key, value);
    }
    
    /**
     * 处理Map数据
     */
    private void processMapData(ConcurrentMap<String, Object> data) {
        if (data.isEmpty()) {
            return;
        }
        
        // 示例:打印或持久化数据
        System.out.println("Processing " + data.size() + " records:");
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
            // 这里可以添加数据库持久化、发送消息等逻辑
        }
        
        // 清空处理完的数据(可选,因为已经是局部变量)
        data.clear();
    }
}
1.2 定时任务配置
复制代码
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class MapScheduledTask {
    
    private final AtomicMapManager mapManager = new AtomicMapManager();
    
    /**
     * 每5分钟执行一次清理和处理
     */
    @Scheduled(fixedRate = 5 * 60 * 1000) // 5分钟
    public void scheduledMapProcessing() {
        try {
            mapManager.scheduledClearAndProcess();
        } catch (Exception e) {
            // 记录日志但不要抛出异常,避免定时任务终止
            System.err.println("定时任务执行失败: " + e.getMessage());
        }
    }
    
    /**
     * 更精细的定时配置
     */
    @Scheduled(cron = "0 */5 * * * ?") // 每5分钟的0秒执行
    public void scheduledWithCron() {
        mapManager.scheduledClearAndProcess();
    }
}
1.3 Spring Boot 配置
复制代码
@Configuration
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {
    
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
    }
    
    @Bean(destroyMethod = "shutdown")
    public Executor taskExecutor() {
        return Executors.newScheduledThreadPool(5);
    }
}

方案二:分段处理(大数据量优化)

2.1 分段Map管理器
复制代码
public class SegmentedMapManager {
    // 分成多个段,减少锁竞争
    private static final int SEGMENTS = 16;
    private static final ConcurrentMap<String, Object>[] segmentMaps = 
        new ConcurrentHashMap[SEGMENTS];
    
    static {
        for (int i = 0; i < SEGMENTS; i++) {
            segmentMaps[i] = new ConcurrentHashMap<>();
        }
    }
    
    /**
     * 根据key哈希选择段
     */
    private int getSegmentIndex(String key) {
        return Math.abs(key.hashCode()) % SEGMENTS;
    }
    
    /**
     * 写入数据
     */
    public void putData(String key, Object value) {
        int segmentIndex = getSegmentIndex(key);
        segmentMaps[segmentIndex].put(key, value);
    }
    
    /**
     * 分段处理:减少单次处理的数据量
     */
    public void scheduledSegmentProcessing() {
        for (int i = 0; i < SEGMENTS; i++) {
            processSegment(i);
        }
    }
    
    /**
     * 处理单个段
     */
    private void processSegment(int segmentIndex) {
        ConcurrentMap<String, Object> currentSegment = segmentMaps[segmentIndex];
        
        // 创建新段替换旧段
        ConcurrentMap<String, Object> newSegment = new ConcurrentHashMap<>();
        segmentMaps[segmentIndex] = newSegment;
        
        // 处理旧段数据
        if (!currentSegment.isEmpty()) {
            processMapData(currentSegment);
        }
    }
    
    /**
     * 并行处理所有段(性能优化)
     */
    public void scheduledParallelProcessing() {
        List<CompletableFuture<Void>> futures = new ArrayList<>();
        
        for (int i = 0; i < SEGMENTS; i++) {
            final int segmentIndex = i;
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                processSegment(segmentIndex);
            });
            futures.add(future);
        }
        
        // 等待所有段处理完成
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
    }
}

方案三:双缓冲方案(零等待)

3.1 双缓冲实现
复制代码
public class DoubleBufferMapManager {
    // 双缓冲:两个Map交替使用
    private volatile ConcurrentMap<String, Object> currentBuffer = new ConcurrentHashMap<>();
    private volatile ConcurrentMap<String, Object> backBuffer = new ConcurrentHashMap<>();
    
    // 用于原子切换缓冲区
    private final AtomicBoolean isSwitching = new AtomicBoolean(false);
    
    /**
     * 写入数据(总是写入当前缓冲区)
     */
    public void putData(String key, Object value) {
        currentBuffer.put(key, value);
    }
    
    /**
     * 切换缓冲区并处理数据
     */
    public void switchAndProcess() {
        // 使用CAS确保只有一个线程执行切换
        if (!isSwitching.compareAndSet(false, true)) {
            return; // 已经有线程在执行切换
        }
        
        try {
            // 交换缓冲区引用
            ConcurrentMap<String, Object> bufferToProcess = currentBuffer;
            currentBuffer = backBuffer;
            backBuffer = new ConcurrentHashMap<>(); // 准备新的后备缓冲区
            
            // 处理数据
            if (!bufferToProcess.isEmpty()) {
                processMapData(bufferToProcess);
            }
        } finally {
            isSwitching.set(false);
        }
    }
    
    /**
     * 获取当前缓冲区大小(用于监控)
     */
    public int getCurrentBufferSize() {
        return currentBuffer.size();
    }
}

方案四:带版本控制的事务方案

4.1 版本控制实现
复制代码
public class VersionedMapManager {
    // 带版本号的Map条目
    private static class VersionedEntry {
        final Object value;
        final long version;
        
        VersionedEntry(Object value, long version) {
            this.value = value;
            this.version = version;
        }
    }
    
    private final ConcurrentMap<String, VersionedEntry> dataMap = 
        new ConcurrentHashMap<>();
    private final AtomicLong versionCounter = new AtomicLong(0);
    
    /**
     * 写入数据(带版本号)
     */
    public void putData(String key, Object value) {
        long newVersion = versionCounter.incrementAndGet();
        dataMap.put(key, new VersionedEntry(value, newVersion));
    }
    
    /**
     * 基于版本号获取快照
     */
    public Map<String, Object> getSnapshotBefore(long maxVersion) {
        Map<String, Object> snapshot = new HashMap<>();
        
        for (Map.Entry<String, VersionedEntry> entry : dataMap.entrySet()) {
            if (entry.getValue().version <= maxVersion) {
                snapshot.put(entry.getKey(), entry.getValue().value);
            }
        }
        
        return snapshot;
    }
    
    /**
     * 清理旧版本数据
     */
    public void cleanupBeforeVersion(long cleanupVersion) {
        Iterator<Map.Entry<String, VersionedEntry>> iterator = 
            dataMap.entrySet().iterator();
        
        while (iterator.hasNext()) {
            Map.Entry<String, VersionedEntry> entry = iterator.next();
            if (entry.getValue().version <= cleanupVersion) {
                iterator.remove();
            }
        }
    }
    
    /**
     * 定时任务:版本控制方式处理
     */
    public void scheduledVersionedProcessing() {
        long currentVersion = versionCounter.get();
        
        // 获取当前版本之前的快照
        Map<String, Object> snapshot = getSnapshotBefore(currentVersion);
        
        if (!snapshot.isEmpty()) {
            processMapData(new ConcurrentHashMap<>(snapshot));
            
            // 清理已处理的数据(可选的,根据需求决定)
            cleanupBeforeVersion(currentVersion);
        }
    }
}

🎈 完整的生产级实现

1. 综合管理器(推荐用于生产)
复制代码
@Component
@Slf4j
public class ProductionMapManager {
    private final AtomicReference<ConcurrentMap<String, Object>> mapRef = 
        new AtomicReference<>(new ConcurrentHashMap<>());
    
    // 监控指标
    private final AtomicLong totalProcessed = new AtomicLong(0);
    private final AtomicLong lastProcessCount = new AtomicLong(0);
    private final AtomicLong errorCount = new AtomicLong(0);
    
    /**
     * 写入数据
     */
    public void put(String key, Object value) {
        try {
            mapRef.get().put(key, value);
        } catch (Exception e) {
            log.error("写入数据失败 key: {}", key, e);
            errorCount.incrementAndGet();
        }
    }
    
    /**
     * 定时处理任务(带监控和容错)
     */
    @Scheduled(fixedRate = 300000) // 5分钟
    public void scheduledProcessing() {
        long startTime = System.currentTimeMillis();
        
        try {
            // 原子替换Map
            ConcurrentMap<String, Object> currentData = 
                mapRef.getAndSet(new ConcurrentHashMap<>());
            
            int recordCount = currentData.size();
            if (recordCount == 0) {
                log.debug("没有数据需要处理");
                return;
            }
            
            log.info("开始处理 {} 条记录", recordCount);
            
            // 处理数据
            boolean success = processWithRetry(currentData);
            
            if (success) {
                totalProcessed.addAndGet(recordCount);
                lastProcessCount.set(recordCount);
                log.info("成功处理 {} 条记录, 耗时 {}ms", 
                    recordCount, System.currentTimeMillis() - startTime);
            } else {
                errorCount.incrementAndGet();
                log.error("处理数据失败, 记录数: {}", recordCount);
            }
            
        } catch (Exception e) {
            errorCount.incrementAndGet();
            log.error("定时任务执行异常", e);
        }
    }
    
    /**
     * 带重试的数据处理
     */
    private boolean processWithRetry(ConcurrentMap<String, Object> data) {
        int maxRetries = 3;
        
        for (int attempt = 1; attempt <= maxRetries; attempt++) {
            try {
                processMapData(data);
                return true;
            } catch (Exception e) {
                log.warn("数据处理第 {} 次尝试失败", attempt, e);
                
                if (attempt == maxRetries) {
                    log.error("数据处理最终失败,已重试 {} 次", maxRetries);
                    return false;
                }
                
                // 指数退避重试
                try {
                    Thread.sleep(1000L * attempt);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    return false;
                }
            }
        }
        return false;
    }
    
    /**
     * 获取监控指标
     */
    public Map<String, Object> getMetrics() {
        Map<String, Object> metrics = new HashMap<>();
        metrics.put("currentMapSize", mapRef.get().size());
        metrics.put("totalProcessed", totalProcessed.get());
        metrics.put("lastProcessCount", lastProcessCount.get());
        metrics.put("errorCount", errorCount.get());
        metrics.put("timestamp", System.currentTimeMillis());
        return metrics;
    }
    
    /**
     * 手动触发处理(用于测试或紧急情况)
     */
    public boolean triggerManualProcessing() {
        log.warn("手动触发数据处理");
        scheduledProcessing();
        return true;
    }
}
2 监控端点
复制代码
@RestController
@RequestMapping("/api/map-manager")
@Slf4j
public class MapManagerController {
    
    @Autowired
    private ProductionMapManager mapManager;
    
    /**
     * 获取监控指标
     */
    @GetMapping("/metrics")
    public ResponseEntity<Map<String, Object>> getMetrics() {
        return ResponseEntity.ok(mapManager.getMetrics());
    }
    
    /**
     * 手动触发处理
     */
    @PostMapping("/process-now")
    public ResponseEntity<String> processNow() {
        try {
            boolean success = mapManager.triggerManualProcessing();
            return success ? 
                ResponseEntity.ok("处理任务已触发") :
                ResponseEntity.status(500).body("处理任务触发失败");
        } catch (Exception e) {
            log.error("手动触发处理失败", e);
            return ResponseEntity.status(500).body("处理失败: " + e.getMessage());
        }
    }
    
    /**
     * 健康检查端点
     */
    @GetMapping("/health")
    public ResponseEntity<Map<String, String>> healthCheck() {
        Map<String, String> health = new HashMap<>();
        health.put("status", "UP");
        health.put("timestamp", Instant.now().toString());
        return ResponseEntity.ok(health);
    }
}
3. 并发测试
复制代码
@SpringBootTest
class MapManagerTest {
    
    @Autowired
    private ProductionMapManager mapManager;
    
    @Test
    void testConcurrentAccess() throws InterruptedException {
        int threadCount = 10;
        int operationsPerThread = 1000;
        
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        
        // 并发写入
        for (int i = 0; i < threadCount; i++) {
            final int threadId = i;
            executor.submit(() -> {
                for (int j = 0; j < operationsPerThread; j++) {
                    String key = "thread-" + threadId + "-key-" + j;
                    mapManager.put(key, "value-" + j);
                }
            });
        }
        
        executor.shutdown();
        assertTrue(executor.awaitTermination(1, TimeUnit.MINUTES));
        
        // 触发处理
        mapManager.triggerManualProcessing();
        
        // 验证指标
        Map<String, Object> metrics = mapManager.getMetrics();
        assertTrue((Long)metrics.get("totalProcessed") > 0);
    }
}
相关推荐
leonardee6 小时前
Spring Security安全框架原理与实战
java·后端
q***5186 小时前
Spring Cloud gateway 路由规则
java
空空kkk7 小时前
SpringMVC框架——入门
java·spring
liyi_hz20087 小时前
云原生 + 国产化适配:O2OA (翱途)开发平台后端技术栈深度解析
java·后端·开源软件
⑩-7 小时前
缓存穿透,击穿,雪崩
java·redis
合作小小程序员小小店7 小时前
web网页开发,在线%考试管理%系统,基于Idea,html,css,jQuery,java,jsp,servlet,mysql。
java·前端·intellij-idea
程序媛_MISS_zhang_01107 小时前
vant-ui中List 组件可以与 PullRefresh 组件结合使用,实现下拉刷新的效果
java·linux·ui
曹牧7 小时前
Java中处理URL转义并下载PDF文件
java·开发语言·pdf
雾山大叔8 小时前
Python学习 - 面向对象学习-文件分类小测试
java·前端·spring
Eric_Makabaka8 小时前
计算机网络重要知识点
java·网络·计算机网络