多线程环境中的Java CRC32计算

时间:2017-02-12 18:49:57

标签: java multithreading crc32

我有一个用于crc32计算的以下util类:

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作为静态字段移动到类级别......这样的事情:

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();
    }

}

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

3 个答案:

答案 0 :(得分:4)

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

ThreadLocal<Checksum>是你的答案。

答案 1 :(得分:3)

显然你不能在多线程环境中这样做,因为CRC32类不是线程安全的。

简短回答:它不是线程安全的,因为它的javadoc不包含这个提示。

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

private int crc;

未同步。

UPD:但您可以将ThreadLocal<Checksum>用作@Dariusz suggested in his answer

答案 2 :(得分:1)

正如其他人所说,CRC32不是线程安全的,因此您必须同步或使用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()比你意识到的要复杂得多,并且担心分配一个简单的,短命的CRC32对象不是表现的主要贡献者。