11

解决方案

  • MessageDigest => 根据需要经常创建新实例
  • KeyFactory => 使用单个共享实例
  • SecureRandom => 使用StackObjectPool
  • 密码 => 使用StackObjectPool

问题

在编写安全框架时,我经常面临两难境地:“汇集或不汇集”

基本上这个问题分为两个“组”:

  1. 第 1 组:SecureRandom因为调用nextBytes(...)是同步的,它可能成为 WebApp / 多线程应用程序的瓶颈

  2. 第 2 组:加密服务提供商,如MessageDigest, Signature, Cipher, KeyFactory, ... (因为getInstance()? 的成本)

你有什么意见 ?

你对这些问题有什么习惯?

编辑 09/07/2013

我终于花时间自己测试了@QwerkyShare课程,我发现结果相当……令人惊讶。

这门课缺少我主要关心的问题:像GenericObjectPoolStackObjectPool这样的池。

因此,我对课程进行了重新设计以测试所有 4 种替代方案:

我不得不将循环数降低到 100000,因为 1M 占用池的时间太多。

我还在Thread.yield()每个循环的末尾添加了一个,以使负载具有更好的形状。

结果(累积运行时间):

  • 信息摘要
    • 新实例:420 秒
    • 单实例:550 秒
    • StackObjectPool : 800 秒
    • 通用对象池:1900 秒
  • 密钥工厂
    • 新实例:400s
    • 单实例:350 秒
    • StackObjectPool : 2900 秒
    • 通用对象池:3500 秒
  • 安全随机
    • StackObjectPool : 1600 秒
    • 新实例:2300 秒
    • 通用对象池:2300s
    • 单实例:2800 秒
  • 密码
    • StackObjectPool : 2800 秒
    • 通用对象池:3500 秒
    • 单实例:5100 秒
    • 新实例:8000 秒

结论

对于 MessageDigest 和 KeyFactory,池是性能杀手,甚至比具有同步瓶颈的单个实例还要糟糕,而对于 SecureRandom 和 Cipher,它们确实很有用

4

2 回答 2

6

如果您让 100 个线程访问一个共享MessageDigest并让它们分别计算 1,000,000 个哈希值,那么在我的机器上,第一个线程在 70,160 毫秒内完成,最后一个线程在 98,748 毫秒内完成。

如果线程每次都创建一个新实例MessageDigest,那么第一个线程在 43,392 毫秒和最后一个 58,691 毫秒内完成。

编辑:
事实上,在这个例子中,只有两个线程,创建新实例的例子运行得更快。

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Share {

  final byte[] bytes = new byte[100];
  final MessageDigest sharedDigest;
  final ExecutorService pool;
  int threads = 100;

  Share() throws NoSuchAlgorithmException {
    sharedDigest = MessageDigest.getInstance("MD5");
    pool = Executors.newFixedThreadPool(threads);
  }

  void go() {

    for (int i=0; i<threads; i++) {
      pool.execute(new Runnable() {
        public void run() {
          long start = System.currentTimeMillis();
          for (int i=0; i<1000000; i++) {
            /*
            synchronized (sharedDigest) {
              sharedDigest.reset();
              sharedDigest.update(bytes);
              sharedDigest.digest();
            }*/
            try {
              MessageDigest digest = MessageDigest.getInstance("MD5");
              digest.reset();
              digest.update(bytes);
              digest.digest();
            } catch (Exception ex) {
              ex.printStackTrace();
            }
          }
          long end = System.currentTimeMillis();
          System.out.println(end-start);
          pool.shutdown();
        }
      });
    }

  }

  public static void main(String[] args) throws Exception {
    Share share = new Share();
    share.go();
  }

}
于 2012-12-17T12:45:48.140 回答
0

这个测试似乎有利于缓存

long t0 = System.currentTimeMillis();
byte[] bytes = new byte[100];
MessageDigest md = MessageDigest.getInstance("MD5");
for(int i = 0; i < 1000000; i++) {
    //MessageDigest md = MessageDigest.getInstance("MD5");
    md.reset();
    md.update(bytes);
    md.digest();
}
System.out.println(System.currentTimeMillis() - t0);

当 md 在循环之外时,它打印 579,当在循环内时 - 953。

于 2012-12-17T11:26:52.260 回答