如何测试一个方法是否是线程安全的?(通过之后的研究发现第三方jar包 GroboUtil5可以更好的完成此任务准备一个方法

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<Object, Object> sortedMap = new TreeMap<Object,Object>();
                        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 < times; j++) {
            str = MD5ForString(str);
        }
        return str;
    }
}

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


总结:

这个问题有一些解决方法,最容易想到的就是我们常用的

synchronized 同步块,在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的,当同步非常激烈的时候,synchronized的性能则会急剧下降。采用API建议的方法,在

MD5Util 中每个使用

MessageDigest 的方法里都创建一个新的对象解决可能更合适