在Java中需要线程安全的MessageDigest

44

我需要在多线程环境下使用MessageDigest对多个密钥进行哈希,这是一个性能关键的场景。我了解到MessageDigest不是线程安全的,因为它在其对象中存储其状态。如何实现线程安全的密钥哈希可能是最佳方式?

用例:

MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");

//somewhere later, just need to hash a key, nothing else
messageDigest.update(key);
byte[] bytes = messageDigest.digest(); 

具体来说:

  1. ThreadLocal是否保证可用?它会有性能损失吗?
  2. 由getInstance返回的对象是不同的,它们互不干扰吗?文档中说“new”对象,但我不确定它是否只是(共享的)共享具体类的包装器?
  3. 如果getInstance()返回“真正”的新对象,每次需要计算哈希时都创建一个新实例是否明智?在性能损失方面——代价有多大?

我的用例非常简单——只需要哈希一个简单的键。我无法承担使用同步。

谢谢,

3个回答

67
每次需要时创建一个新的MessageDigest实例。
getInstance()返回的所有实例都是不同的。它们需要是这样,因为它们维护单独的摘要(如果这对您来说还不够,这里是源代码链接)。 ThreadLocal在与线程池一起使用时可以提供性能优势,以维护昂贵的构造对象。 MessageDigest 的构造成本并不特别高(再次查看源代码)。

1
如果我看到getInstance()的代码,它似乎并没有创建新对象,而是调用了Security来获取对象 Object[] objs = Security.getImpl我编写了以下测试用例: MessageDigest messageDigest1 = MessageDigest.getInstance("SHA-1"); MessageDigest messageDigest2 = MessageDigest.getInstance("SHA-1"); // update and digest 并且发现两个messageDigest对象都不同,它们内部的对象/缓冲区也不同。所以,我认为ThreadLocal应该可以解决这个问题。 是的,这是一个带有线程池的Web服务器。我将使用ThreadLocal。谢谢。 - Anil Padia
9
@AnilPadia - 我强烈建议不要使用ThreadLocal。这是过早的优化。我编写了一个微型基准测试,需要约2 秒来创建一个新的MessageDigest。这个时间在使用摘要的代码中将被远远超过。 - parsifal
您认为使用ThreadLocal存在哪些问题?即使我有数百个线程,也将有数百个这样的对象。我发现这些对象的内存占用非常少。ThreadLocal对我来说运行得很好。我还测试了创建对象,只需要4微秒。我真的很想知道您为什么反对ThreadLocal。 - Anil Padia
但是,我意识到你对ThreadLocal很满意,而且我不太可能改变你的看法。所以做让你开心的事情吧。 - parsifal
1
在重型多线程系统中(我已经在jdk1.7.0_80上验证过),底层的java.security.Provider.getService方法是同步的并且似乎是单例的,因此可能会出现显著的性能问题。 - Martin Serrano
显示剩余2条评论

6
作为替代方案,请使用DigestUtils,它是Apache Commons对MessageDigest的线程安全封装。 sha1()可以满足您的需求: byte[] bytes = sha1(key)

4
DigestUtils并不比MessageDigest更加线程安全,因为DigestUtils.getDigest()只是调用了MessageDigest.getInstance()并将一个受检异常转换为未经检查的异常,不会对线程安全性做出任何改变。 - Siddhu
9
这里的重点是MessageDigest不是线程安全的,因此在并发环境中重用相同的实例会导致结果不可预测。每次使用一个新/不同的实例(例如通过调用MessageDigest.getInstance)解决了这个问题。DisgestUtils在意义上是线程安全的,因为它的每个便利方法都使用一个新的MessageDigest实例,每个方法在多次调用后都会调用MessageDigest.getInstance来创建一个新的实例。例如,每次调用DigestUtils.sha256Hex("我的字符串"); 都会使用MessageDigest的不同实例。 - Legna

2
你可以使用我编写的开源库Caesar中的ImmutableMessageDigest。它本质上包装了一个MessageDigest实例,并在每次digest()update()调用之前进行克隆。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接