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 对象不是'不是性能的主要贡献者。
我有以下实用程序 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 对象不是'不是性能的主要贡献者。