class DateFormatTest {
private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.ENGLISH);
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) {
// synchronized (sdf) {//TODO 加锁解决
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();
}
}
}
SimpleDateFormat为何不是线程安全的?
如果我们把SimpleDateFormat定义成static成员变量,那么多个thread之间会共享这个SimpleDateFormat对象, 所以Calendar对象也会共享。
SimpleDateFormat.java
@Override
public StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition pos)
{
// 如此轻易地使用内部变量,肯定不能线程安全
// 线程都对pos进行写操作,必然会影响其他线程的读操作
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
// 这里已经彻底毁坏线程的安全性
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;
}
解决SimpleDateFormat线程不安全的方法:
1)将SimpleDateFormat定义成局部变量
缺点:可能会创建大量的SimpleDateFormat对象
2)加一把线程同步锁:synchronized(lock)
缺点:性能较差,每次都要等待锁释放后其他线程才能进入
3)使用java8的DateTimeFormatter
jdk1.8中新增了 LocalDate 与 LocalDateTime等类来解决日期处理方法,同时引入了一个新的类DateTimeFormatter来解决日期格式化问题。
LocalDateTime,DateTimeFormatter两个类都没有线程问题,只要你自己不把它们创建为共享变量就没有线程问题。
可以使用Instant代替 Date,LocalDateTime代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。
4)使用ThreadLocal,每个线程都将拥有自己的SimpleDateFormat对象副本。