单线程场景
import java.text.SimpleDateFormat;
import java.util.Date;
public class Main{
public static void main(String[] args){
Date date = new Date();
String strDateFormat = "yyyy-MM-dd HH:mm:ss";
//不可以提到外部改成静态变量,因为线程不安全
SimpleDateFormat sdf = new SimpleDateFormat(strDateFormat);
System.out.println(sdf.format(date));
//结果: 2015-03-27 21:13:23
}
}
线程不安全原因
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 FieldDelegate
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.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来进行解析,
其方法中主要步骤不是原子操作。
多线程场景
import java.text.SimpleDateFormat;
import java.util.Date;
public class Main{
/**时间格式化*/
private static final ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal= ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static void main(String[] args){
Date date = new Date();
SimpleDateFormat sdf = simpleDateFormatThreadLocal.get();
System.out.println(sdf.format(date));
}
}
使用DateTimeFormatter代替SimpleDateFormat
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class DateFormatTest {
private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy", Locale.US);
private static String date[] = { "01-01-1999", "09-01-2000", "08-01-2001" , "07-01-2002" , "06-01-2003" , "05-01-2004" , "04-01-2005" , "03-01-2006" , "02-01-2007" };
private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
public static void main(String[] args) {
for (int i = 0; i < date.length; i++) {
final int temp = i;
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
String str1 = date[temp];
//线程安全
LocalDate date = LocalDate.parse(str1, formatter);
String str2 = formatter.format(date);
//线程不安全
// String str2 = sdf.format(sdf.parse(str1));
System.out.println(Thread.currentThread().getName() + ", " + str1 + "," + str2);
if(!str1.equals(str2)){
throw new RuntimeException(Thread.currentThread().getName()
+ ", Expected " + str1 + " but got " + str2);
}
}
} catch (Exception e) {
throw new RuntimeException("parse failed", e);
}
}
}).start();
}
}
}