如何测试一个方法是否是线程安全的?(通过之后的研究发现第三方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 的方法里都创建一个新的对象解决可能更合适