java模拟并发请求测试方法是否线程安全

如何测试一个方法是否是线程安全的?(通过之后的研究发现第三方jar包 GroboUtil5可以更好的完成此任务, 参考:使用GroboUtils多线程并发请求测试springmvc controller


准备一个方法

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;

/**

  • Created by Administrator on 2017/6/16.
    */
    public class SignatureUtil {

    /**

    • 签名生成方法
    • @param parameters
    • @param key
    • @return
      */
      public static String getSignature(SortedMap<Object, Object> parameters, String key) {
      StringBuffer sb = new StringBuffer();
      Set<Map.Entry<Object, Object>> es = parameters.entrySet();
      Iterator<Map.Entry<Object, Object>> it = es.iterator();
      while (it.hasNext()) {
      Map.Entry<Object, Object> entry = it.next();
      String k = (String) entry.getKey();
      Object v = entry.getValue();
      if (null != v && “”.equals(v)) {
      sb.append(k + “=” + v + “&”);
      }
      }
      sb.append(“key=” + key);
      String sign = MD5Util.MD5ForString(sb.toString()).toUpperCase();
      //String sign = MD5Util_static.MD5ForString(sb.toString()).toUpperCase();
      return sign;
      }
      }

当MD5Util.MD5ForString方法为如下时:

import sun.misc.BASE64Encoder;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**

  • Created by Administrator on 2017/6/16.
    */
    public class MD5Util {

    protected static MessageDigest messagedigest = null;

    static {
    try {
    messagedigest = MessageDigest.getInstance(“MD5”);
    } catch (NoSuchAlgorithmException nsaex) {
    nsaex.printStackTrace();
    }
    }

    public static String MD5ForString(String str) {
    try {
    BASE64Encoder base64en = new BASE64Encoder();
    //加密后的字符串
    String value = base64en.encode(messagedigest.digest(str.getBytes(“utf-8”)));
    return value;
    } catch (Exception var4) {
    var4.printStackTrace();
    return null;
    }
    }
    }

测试类:

   @Test
    public void testGetSignature() throws Exception {
        //在线程可以通过await()之前必须调用countDown()的次数
        //具有计数1的 CountdownLatch 可以用作”启动大门”,来立即启动一组线程;
        final CountDownLatch begin = new CountDownLatch(1);  //为0时开始执行
        final ExecutorService exec = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 100; i++) {
            final int NO = i + 1;
            Runnable runnable = new Runnable() {
                public void run() {
                    try {
                        //如果当前计数为零,则此方法立即返回。
                        //如果当前计数大于零,则当前线程将被禁用以进行线程调度,并处于休眠状态,直至发生两件事情之一:
                        //或调用countDown()方法,计数达到零;
                        //或一些其他线程中断当前线程。
                    //等待直到 CountDownLatch减到1
                    begin.await();
                    SortedMap&lt;Object, Object&gt; sortedMap = new TreeMap&lt;Object,Object&gt;();
                    sortedMap.put("appid", "5412916052207510000052159");
                    sortedMap.put("mch_id", "dhfkjadh");
                    sortedMap.put("nonce_str", "fasjkldfh");
                    String sign = SignatureUtil.getSignature(sortedMap, "fhldhfkajhdfkjajsdh");
                    System.out.println("sign" + String.valueOf(NO) + ": " + sign);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        exec.submit(runnable);
    }
    System.out.println("开始执行");
    //减少锁存器的计数,如果计数达到零,释放所有等待的线程。
    // begin减一,开始并发执行
    begin.countDown();
    //此方法不等待先前提交的任务完成执行
    //exec.shutdown();
    //为了保证先前提交的任务完成执行 使用此方法
    exec.awaitTermination(4000, TimeUnit.MILLISECONDS);

}

测试结果:

生成的方法签名存在不同


当给MD5Util.MD5ForString方法加上synchronized后测试结果正确,方法签名相同。原因在于

消息摘要的类 MessageDigest 为static,也就是每次方法调用都使用的是同一个对象,而这个类是非线程安全的(参考)
The MessageDigest classes are NOT thread safe. If they’re going to be used by different threads, just create a new one, instead of trying to reuse them.

于是修改MD5Util.MD5ForString方法

import sun.misc.BASE64Encoder;
import java.security.MessageDigest;

public class MD5Util {

public static String MD5ForString(String str) {
    try {
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        BASE64Encoder base64en = new BASE64Encoder();
        //加密后的字符串
        String value = base64en.encode(md5.digest(str.getBytes("utf-8")));
        return value;
    } catch (Exception var4) {
        var4.printStackTrace();
        return null;
    }
}

public static String MD5ForString(String str, int times) {
    for (int j = 0; j &lt; times; j++) {
        str = MD5ForString(str);
    }
    return str;
}

}
测试结果正确,方法签名相同。


总结:


这个问题有一些解决方法,最容易想到的就是我们常用的 synchronized 同步块,在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的,当同步非常激烈的时候,synchronized的性能则会急剧下降。采用API建议的方法,在 MD5Util 中每个使用
MessageDigest 的方法里都创建一个新的对象解决可能更合适

参考:Java模拟并发请求

猜你喜欢

转载自blog.csdn.net/huanchankuang3257/article/details/82919231
今日推荐