You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程场景下Callable调用工具类通用方法的最优实现

并行任务下日期比较的线程安全与性能优化方案

问题描述

我启动最多200个并行运行的Callable类,这些类会调用工具类的通用方法areEqual。发现不给这个方法加synchronized修饰就会出现错误,即使把方法移到Callable类中问题依然存在。添加synchronized后能正常运行,但担心性能下降——并行任务会互相阻塞,无法达到最大运行速度。

具体错误场景:不加synchronized时,只要不涉及日期比较就不会出错;但混合使用java.util.Date和Timestamp做日期比较(转成Date再转为字符串)时就会报错。加锁后恢复正常,说明方法逻辑本身是正确的。

简化代码片段

CompareTableTF类

public class CompareTableTF {
...
    public void compareTable(...) {
    ...
        while (...) {
        try {
            myTask = new CompareTableTask(...);
            completion.submit(myTask);
        }
        catch (InterruptedException | ExecutionException e) {...}
        ...
    }
}

CompareTableTask类

public class CompareTableTask implements Callable<Integer> {
    private List<Object> row1;

    public CompareTableTask(List<Object> row1) { 
        this.row1=row1;
    }

    @Override
    public Integer call() {
        int result=1;
        List<Object> row2=getRowToCompare(...);
        if (isEqual(row2)) {
            result=0;
        }
        return result;
    }

    private boolean isEqual(List<Object> row2) {
        ...
        for (int i=0; i<n; i++) {
            if (! Tools.areEqual(row1.get(i), row2.get(i))) return false;
        }
        return true;
    }
}

Tools类

public class Tools {
    private final static SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");

    ...
    public synchronized static boolean areEqual(Object obj1, Object obj2) {
        String sObj1, sObj2;
        Double dObj1, dObj2;
        Date dtObj1, dtObj2;
        boolean resultat = true;
        
        // Check if one only is empty
        ...
        // Compare as objects
        if (!obj1.equals(obj2)) {

        // if differents, compare as strings
            sObj1=obj1.toString();
            sObj2=obj2.toString();
            if (!sObj1.equals(sObj2)) {

            // if differents, verify if are instanceof java.util.Date, convert them to strings, compare as strings
            if (obj1 instanceof Date && obj2 instanceof Date) {
                if (obj1 instanceof Timestamp) {dtObj1 = new Date(((Timestamp) obj1).getTime());} else {dtObj1 = (Date) obj1;}
                if (obj2 instanceof Timestamp) {dtObj2 = new Date(((Timestamp) obj2).getTime());} else {dtObj2 = (Date) obj2;}
                sObj1 = dateTimeFormat.format(dtObj1);
                sObj2 = dateTimeFormat.format(dtObj2);
                result = sObj1.equals(sObj2);
            }
            else {
                // if differents (and not date), last compare as Double
                resultat=false;
                try {
                    dObj1 = new Double(sObj1);
                    dObj2 = new Double(sObj2);
                    if (dObj1.equals(dObj2)) {
                        resultat=true;
                    }
                }
                catch (NumberFormatException e) {
                }
            }
        }
        return result;
    }
}

最优实现方案

问题根源在于**SimpleDateFormat是线程不安全的**——多个线程共享同一个实例时,会导致日期格式化逻辑混乱,进而出现比较错误。给areEqual加synchronized只是通过串行化执行规避了这个问题,但牺牲了并行性能。以下是两种更优的解决思路:

1. 为每个线程分配独立的SimpleDateFormat实例

使用ThreadLocal缓存每个线程的格式化实例,避免多线程共享,同时减少频繁创建对象的开销:

public class Tools {
    // 用ThreadLocal存储每个线程独有的SimpleDateFormat实例
    private static final ThreadLocal<SimpleDateFormat> dateTimeFormat = ThreadLocal.withInitial(
        () -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S")
    );

    // 移除synchronized修饰符
    public static boolean areEqual(Object obj1, Object obj2) {
        String sObj1, sObj2;
        Double dObj1, dObj2;
        Date dtObj1, dtObj2;
        boolean resultat = true;
        
        // 原有逻辑不变,仅修改格式化实例的获取方式
        ...
        if (obj1 instanceof Date && obj2 instanceof Date) {
            ...
            // 获取当前线程的格式化实例
            sObj1 = dateTimeFormat.get().format(dtObj1);
            sObj2 = dateTimeFormat.get().format(dtObj2);
            resultat = sObj1.equals(sObj2);
        }
        ...
        return resultat;
    }
}

2. 使用Java 8+线程安全的日期API(推荐)

Java 8引入的DateTimeFormatter是线程安全的,配合新的日期时间类(如LocalDateTime)可以更规范地处理日期转换:

public class Tools {
    private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S");

    public static boolean areEqual(Object obj1, Object obj2) {
        String sObj1, sObj2;
        Double dObj1, dObj2;
        boolean resultat = true;
        
        ...
        if (obj1 instanceof Date && obj2 instanceof Date) {
            // 将Date/Timestamp转换为LocalDateTime
            LocalDateTime dt1 = ((Date) obj1).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
            LocalDateTime dt2 = ((Date) obj2).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
            // 使用线程安全的DateTimeFormatter格式化
            sObj1 = dateTimeFormatter.format(dt1);
            sObj2 = dateTimeFormatter.format(dt2);
            resultat = sObj1.equals(sObj2);
        }
        ...
        return resultat;
    }
}

效果说明

两种方案都无需给areEqual方法加synchronized,每个并行任务可以独立执行,不会互相阻塞,既能保证线程安全,又能充分利用并行性能。其中Java 8+的日期API方案更推荐,它不仅线程安全,还提供了更清晰、类型安全的日期处理逻辑,避免旧API的潜在问题。

内容的提问来源于stack exchange,提问作者vinsse2001

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 23:32:12