为什么SimpleDateFormat是线程不安全的?
因为SimpleDateFormat中有一个对象为Calendar,这个类在format中的作用就是将日期转换为字符串,而在转换过程中会涉及到一个方法:
xxx 此处省去一堆字
calendar.setTime(date);
xxxx 此处省去一堆字
该方法就是将传入的date放入calendar内,试想,如果一个线程将date放入了,然后立刻切换到另一个线程,另一个线程就会将calendar中的date覆盖。这样一来,所做的转换肯定就是不准确的啦。
SimpleDataFormat在什么情况下使用是不安全的?
1、如果实例是单例,那么SimpleDataFormat作为成员变量就是不安全的。
比如下面这个例子:
public class MyThread implements Runnable {
private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd,HHmmss");
@Override
public void run() {
while (true) {
try {
String date = simpleDateFormat.format(new Date(Math.abs(new Random().nextLong())));
System.out.println("日期为:" + date);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread).start();
new Thread(myThread).start();
}
}
一起来看下结果喽:
日期为:948621410818,224915
java.lang.ArrayIndexOutOfBoundsException: 5595819
at sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:453)
at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2397)
at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2312)
at java.util.Calendar.complete(Calendar.java:2268)
at java.util.Calendar.get(Calendar.java:1826)
at java.text.SimpleDateFormat.subFormat(SimpleDateFormat.java:1119)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:966)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:936)
at java.text.DateFormat.format(DateFormat.java:345)
at com.kk.test.MyThread.run(MyThread.java:23)
at java.lang.Thread.run(Thread.java:748)
一起来分析一下,如果是单例,则实例化过程是在堆上分配内存,所以simpleDateFormat也是在堆上分配的。而每个线程都有自身的虚拟机栈,每次需要simpleDateFormat的时候,它会将其从内存中加载进虚拟机栈里面。但是线程可能会修改堆中的值,所以,一个线程使用完后,另一个线程很容易加载错误的date。
2、如果实例是多例,那么如果SimpleDateFormat定义为static时,也是会出现问题的。因为static方法是在方法区分配内存的,多个实例是共享的,所以即使是多例模式,其还是很容易修改SimpleDateFormat里面的值的。
如何解决线程安全问题?
很简单,只需将其放入ThreadLocal里面就行了,ThreadLocal就是线程私有的,也类似于一个Map,每个线程都有自己的内存空间,各个线程间是不会出现共享情况发生的。
public class MyThread implements Runnable {
private ThreadLocal threadLocal = new ThreadLocal() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyyMMdd,HHmmss");
}
};
@Override
public void run() {
while (true) {
try {
String date = ((SimpleDateFormat)threadLocal.get()).format(new Date(Math.abs(new Random().nextLong())));
System.out.println("日期为:" + date);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
}