一、SimpleDateFormat线程不安全验证
/*** SimpleDateFormat线程安全测试* 〈功能详细描述〉** @author 17090889* @see [相关类/方法](可选)* @since [产品/模块版本] (可选)*/public class SimpleDateFormatTest {private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000), new MyThreadFactory("SimpleDateFormatTest"));public void test() {while (true) {poolExecutor.execute(new Runnable() {@Overridepublic void run() {String dateString = simpleDateFormat.format(new Date());try {Date parseDate = simpleDateFormat.parse(dateString);String dateString2 = simpleDateFormat.format(parseDate);System.out.println(dateString.equals(dateString2));} catch (ParseException e) {e.printStackTrace();}}});}}
二、不安全原因(源码解析)
public StringBuffer format(Date date, StringBuffer toAppendTo,FieldPosition pos){pos.beginIndex = pos.endIndex = 0;return format(date, toAppendTo, pos.getFieldDelegate());}// Called from Format after creating a FieldDelegateprivate StringBuffer format(Date date, StringBuffer toAppendTo,FieldDelegate delegate) {// Convert input date to time field listcalendar.setTime(date);boolean useDateFormatSymbols = useDateFormatSymbols();for (int i = 0; i < compiledPattern.length; ) {int tag = compiledPattern[i] >>> 8;int count = compiledPattern[i++] & 0xff;if (count == 255) {count = compiledPattern[i++] << 16;count |= compiledPattern[i++];}switch (tag) {case TAG_QUOTE_ASCII_CHAR:toAppendTo.append((char)count);break;case TAG_QUOTE_CHARS:toAppendTo.append(compiledPattern, i, count);i += count;break;default:subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);break;}}return toAppendTo;}
可以看到,多个线程之间共享变量calendar,并修改calendar。因此在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。
此外,parse方法也是线程不安全的,parse方法实际调用的是CalenderBuilder的establish来进行解析,其方法中主要步骤不是原子操作。
三、解决方案:
3.1、将SimpleDateFormat定义成局部变量
3.2、 加一把线程同步锁:synchronized(lock)
3.3、使用ThreadLocal,每个线程都拥有自己的SimpleDateFormat对象副本。如:
/**
* SimpleDateFormat线程安全测试
* 〈功能详细描述〉
*
* @author 17090889
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
public class SimpleDateFormatTest {
private static final ThreadLocal<SimpleDateFormat> THREAD_LOCAL = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
// private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000), new MyThreadFactory("SimpleDateFormatTest"));
public void test() {
while (true) {
poolExecutor.execute(new Runnable() {
@Override
public void run() {
SimpleDateFormat simpleDateFormat = THREAD_LOCAL.get();
if (simpleDateFormat == null) {
simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
String dateString = simpleDateFormat.format(new Date());
try {
Date parseDate = simpleDateFormat.parse(dateString);
String dateString2 = simpleDateFormat.format(parseDate);
System.out.println(dateString.equals(dateString2));
} catch (ParseException e) {
e.printStackTrace();
} finally {
local.remove();
}
}
});
}
}
}
3.4、使用DateTimeFormatter代替SimpleDateFormat
DateTimeFormatter是线程安全的,默认提供了很多格式化方法,也可以通过ofPattern方法创建自定义格式化方法。
1、格式化日期示例:
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime); // 2019-11-20T15:04:29.017
DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
String strDate=localDateTime.format(dtf);
System.out.println(strDate); // 2019/23/20 15:23:46
2、解析日期
DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
LocalDateTime localDateTime=LocalDateTime.parse("2019/11/20 15:23:46",dtf);
System.out.println(localDateTime); // 2019-11-20T15:23:46
二、使用JDK8全新的日期和时间API
2.1、LocalDate、LocalTime、LocalDateTime
Date如果不格式化,打印出的日期可读性极差,可使用LocalDateTime代替。
1、LocalDateTime:获取年月日时分秒等于LocalDate+LocalTime
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime); // 2019-11-20T15:04:29.017
LocalDate localDate = localDateTime.toLocalDate();
LocalTime localTime = localDateTime.toLocalTime();
2、LocalDate:获取年月日
LocalDate localDate=LocalDate.now();
System.out.println(localDate); // 2019-11-20
3、LocalTime:获取时分秒
LocalTime localTime = LocalTime.now();
System.out.println(localTime); // 15:14:17.081
int hour = localTime.getHour();
int minute = localTime.getMinute();
int second = localTime.getSecond();
注:参考资料
https://www.cnblogs.com/yangyongjie/p/11017409.html
