最近在工作中遇到了线程安全的问题,是在一个方法中调用了静态方法解析Date的字符串。
因为 SimpleDateFormat这个类是线程不安全的,所以不能在静态方法中定义全局的成员变量。
@Test void contextLoads() { ExecutorService executorService= Executors.newFixedThreadPool(6); for (int i = 0; i < 6; i++) { Runnable runnable=new Runnable() { @Override public void run() { Date date= DateUtil.ParseDate("20210501"); System.out.println("子线程"+Thread.currentThread().getName()+date); } }; executorService.execute(runnable); } }
原因:
SimpleDateFormat继承了DateFormat,DateFormat内部有一个Calendar对象的引用,主要用来存储和SimpleDateFormat相关的日期信息
SimpleDateFormat对parse()方法的实现。关键代码如下:
@Override
public Date parse(String text, ParsePosition pos) {
...省略中间代码
Date parsedDate;
try {
...
parsedDate = calb.establish(calendar).getTime();
} catch (IllegalArgumentException e) {
...
}
return parsedDate;
}
establish()的实现如下:
Calendar establish(Calendar cal) {
...
cal.clear();
for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
for (int index = 0; index <= maxFieldIndex; index++) {
if (field[index] == stamp) {
cal.set(index, field[MAX_FIELD + index]);
break;
}
}
}
...
return cal;
}
在多个线程共享SimpleDateFormat时,同时也共享了Calendar引用,在如上代码中,calendar首先会进行clear操作,然后进行set操作,在多线程情况下,set操作会覆盖之前的值,而且在后续对日期进行操作时,也可能会因为clear操作被清除导致异常
解决方法:
1.使用synchronized来保证线程安全。
2.把全局变量换为使用成员变量。
3.使用ThreadLocal
public class DateUtil {
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
// public static synchronized Date ParseDate(String str){// 方案1,加锁
public static Date ParseDate(String str){
try {
SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd");//方案2,使用成员变量。
return sf.parse(str);
//return simpleDateFormat.parse(str);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}