Java多线程环境下的CRC32计算

Java CRC32 calculation in a multi threading envirinment

我有以下实用程序 class 用于 crc32 计算:

import java.util.zip.CRC32;
import java.util.zip.Checksum;

public class StringUtils {

    public static long crc32(String input) {
        byte[] bytes = input.getBytes();
        Checksum checksum = new CRC32();
        checksum.update(bytes, 0, bytes.length);

        return checksum.getValue();
    }

}

性能对我来说是一个非常重要的标准。

现在我正在考虑对这种方法进行重构,并且我正在考虑将 checksum 移动到 class 级别作为静态字段...像这样:

public class StringUtils {

    public static Checksum checksum = new CRC32();

    public static long crc32(String input) {
        byte[] bytes = input.getBytes();
        checksum.update(bytes, 0, bytes.length);

        return checksum.getValue();
    }

}

但我不确定它能否在并发多线程环境中正常工作。请指教 - 这种重构是不是一个好主意。

不,您的代码不是线程安全的。幸运的是,您可以通过一个简单的 class 使其成为线程安全的,并且几乎没有任何性能损失:

ThreadLocal<Checksum> 就是你的答案。

显然你不能在 multi-thread 环境中这样做,因为 CRC32 class 不是 thread-safe.

简短回答:它不是 thread-safe,因为它的 javadoc 不包含此提示。

更详细:如果你将开源 CRC32 class,你会看到,这个 class 不包含任何同步块,它不是原子的,并且包含对象变量

private int crc;

未同步。

UPD: 但是你可以使用 ThreadLocal<Checksum> 作为@Dariusz .

正如其他人所说,CRC32 不是 thread-safe,因此您必须同步或使用 ThreadLocal,但这些都不太可能提供帮助。

如果您查看 CRC32 的实现,它有 一个 字段。在你做任何事情之前,对你的代码进行基准测试。在 Java 复杂的 GC、JIT 和逃逸分析之间,很难预测您是否会看到任何好处。

重写这个以避免数组分配可能会给你带来更大的好处:

byte[] bytes = input.getBytes();

编辑:除非万不得已,否则请不要这样做。

这会展开 String 的内部 getBytes() 以避免一些中间缓冲,并利用 CRC32 对直接字节缓冲区进行优化:

public class StringUtils {
    private static final ThreadLocal<ByteBuffer> BUFFER = ThreadLocal.withInitial(() -> ByteBuffer.allocateDirect(4094));

    public static long crc32(String input) {
        CharBuffer inputBuffer = CharBuffer.wrap(input);
        ByteBuffer buffer = BUFFER.get();
        CRC32 crc32 = new CRC32();
        CharsetEncoder encoder = Charset.defaultCharset().newEncoder();

        CoderResult coderResult;
        do {
            try {
                coderResult = encoder.encode(inputBuffer, buffer, true);
                buffer.flip();
                crc32.update(buffer);
            } finally {
                buffer.reset();
            }
        } while (coderResult.isOverflow());

        return crc32.getValue();
    }
}

您可以通过手动进行编码(这对于 ASCII 来说是微不足道的)来做得更好。使性能复杂化的是平衡将字节复制到缓冲区中只是为了通过对实际 CRC32 实现的 JNI 调用将它们读出。由于 JNI 开销,中间缓冲区实际上可能更快。在执行此操作之前,请务必阅读 direct bytebuffers;如果您实际上没有重用缓冲区,这可能会很慢。

当你真正深入研究发生的事情时,你会发现 getBytes() 比你想象的要复杂得多,并且担心分配一个琐碎的 short-lived CRC32 对象不是'不是性能的主要贡献者。