1.SimpleDateFormat為什么不是線程安全的庆械?
- 如果我們把SimpleDateFormat定義成static成員變量氯析,那么多個(gè)thread之間會(huì)共享這個(gè)SimpleDateFormat對(duì)象, 所以Calendar對(duì)象也會(huì)共享嫂冻。
public static SimpleDateFormat formater = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
System.out.println(formater.format(new Date())+" Exception made...");
- DateFormat.java
public final String format(Date date)
{
return format(date, new StringBuffer(),
DontCareFieldPosition.INSTANCE).toString();
}
public abstract StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition fieldPosition);
- SimpleDateFormat.java
@Override
public StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition pos)
{
// 如此輕易地使用內(nèi)部變量伊脓,肯定不能線程安全
// 線程都對(duì)pos進(jìn)行寫操作碗短,必然會(huì)影響其他線程的讀操作
pos.beginIndex = pos.endIndex = 0;
return format(date, toAppendTo, pos.getFieldDelegate());
}
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
// 這里已經(jīng)徹底毀壞線程的安全性
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;
}
1.1 復(fù)現(xiàn)錯(cuò)誤
public class DateFormatTest {
private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
private static String date[] = { "01-Jan-1999", "09-Jan-2000", "08-Jan-2001" , "07-Jan-2002" , "06-Jan-2003" , "05-Jan-2004" , "04-Jan-2005" , "03-Jan-2006" , "02-Jan-2007" };
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];
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();
}
}
}
2.SimpleDateFormat線程不安全的解決方法
2.1 將SimpleDateFormat定義成局部變量:
SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
String str1 = "01-Jan-2010";
String str2 = sdf.format(sdf.parse(str1));
缺點(diǎn):每調(diào)用一次方法就會(huì)創(chuàng)建一個(gè)SimpleDateFormat對(duì)象蜈彼,方法結(jié)束又要作為垃圾回收俺驶。
2.2 加一把線程同步鎖:synchronized(lock)
public class SyncDateFormatTest {
private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" };
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) {
synchronized (sdf) {
String str1 = date[temp];
Date date = sdf.parse(str1);
String str2 = sdf.format(date);
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();
}
}
}
缺點(diǎn):性能較差幸逆,每次都要等待鎖釋放后其他線程才能進(jìn)入
2.3 使用ThreadLocal
每個(gè)線程都將擁有自己的SimpleDateFormat對(duì)象副本。
public class DateUtil {
private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<SimpleDateFormat>();
public static Date parse(String str) throws Exception {
SimpleDateFormat sdf = local.get();
if (sdf == null) {
sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
local.set(sdf);
}
return sdf.parse(str);
}
public static String format(Date date) throws Exception {
SimpleDateFormat sdf = local.get();
if (sdf == null) {
sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
local.set(sdf);
}
return sdf.format(date);
}
}
public class ThreadLocalDateFormatTest {
private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" };
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];
Date date = DateUtil.parse(str1);
String str2 = DateUtil.format(date);
System.out.println(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();
}
}
}
3.使用DateTimeFormatter代替SimpleDateFormat
代碼參考DateTimeFormatterTest.java
jdk1.8中新增了 LocalDate 與 LocalDateTime等類來(lái)解決日期處理方法还绘,同時(shí)引入了一個(gè)新的類DateTimeFormatter來(lái)解決日期格式化問(wèn)題栖袋。
LocalDateTime,DateTimeFormatter兩個(gè)類都沒(méi)有線程問(wèn)題蚕甥,只要你自己不把它們創(chuàng)建為共享變量就沒(méi)有線程問(wèn)題。
可以使用Instant代替 Date栋荸,LocalDateTime代替 Calendar菇怀,DateTimeFormatter 代替 SimpleDateFormat晌块。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MM dd");
LocalDate date = LocalDate.parse("2017 06 17", formatter);
System.out.println(formatter.format(date));