SimpleDateFormat 是 Java 中一个非常常用的类,该类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微妙和难以调试的问题,因为 DateFormat 和 SimpleDateFormat 类不都是线程安全的,在多线程环境下调用 format() 和 parse() 方法应该使用同步代码来避免问题。
public class DateUtil {
public static String formatDate(Date date)throws ParseException{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
public static Date parse(String strDate) throws ParseException{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.parse(strDate);
}
}
我们都知道在程序中我们应当尽量少的创建SimpleDateFormat 实例,因为创建这么一个实例需要耗费很大的代价。相比于共享一个变量的开销要比每次创建一个新变量要小很多。下面优化静态版本的SimpleDateFormat。
public class DateUtil {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String formatDate(Date date)throws ParseException{
return sdf.format(date);
}
public static Date parse(String strDate) throws ParseException{
return sdf.parse(strDate);
}
}
这时,这个方法不管你调用多少次,java虚拟机中始终只存在一个SimpleDateFormat对象,效率肯定要比第一种高多了。在正常情况下,这个方法会工作得很好,代码结构看起来也确实不错,但很不幸,在多线程下这断代码肯定报错:
测试代码:
public static class TestSimpleDateFormatThreadSafe extends Thread {
@Override
public void run() {
while (true) {
try {
this.join(2000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
System.out.println(this.getName() + ":" + DateUtil.parse("2013-05-24 06:02:20"));
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
for(int i = 0; i < 3; i++){
new TestSimpleDateFormatThreadSafe().start();
}
}
解决方法 :
使用ThreadLocal, 也是将共享变量变为独享,线程独享肯定能比方法独享在并发环境中能减少不少创建对象的开销。
private static final Map<String, ThreadLocal<SimpleDateFormat>> pool = new HashMap<>();
private static final Object lock = new Object();
/**
* @Description 格式化日期,
* @param date
* @param pattern yyyy-MM-dd HH:mm:ss
* @return
*/
public static String format(Date date, String pattern) {
String ret = "";
SimpleDateFormat format = getDateFormat(pattern);
ret = format.format(date);
return ret;
}
public static SimpleDateFormat getDateFormat(String pattern) {
ThreadLocal<SimpleDateFormat> tl = pool.get(pattern);
if (tl == null) {
synchronized (lock) {
tl = pool.get(pattern);
if (tl == null) {
final String p = pattern;
tl = new ThreadLocal<SimpleDateFormat>() {
@Override
protected synchronized SimpleDateFormat initialValue() {
return new SimpleDateFormat(p);
}
};
pool.put(p, tl);
}
}
}
return tl.get();
}