在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。
SimpleDateFormat类内部有一个Calendar对象引用,它用来储存和这个SimpleDateFormat相关的日期信息,例如sdf.parse(dateStr),sdf.format(date) 诸如此类的方法参数传入的日期相关String,Date等等, 都是交由Calendar引用来储存的.这样就会导致一个问题,如果你的SimpleDateFormat是个static的, 那么多个thread 之间就会共享这个SimpleDateFormat,同时也是共享这个Calendar引用。单例、多线程、又有成员变量(这个变量在方法中是可以修改的),在高并发的情况下,容易出现幻读成员变量的现象,故说SimpleDateFormat是线程不安全的对象。
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DataFormatTest extends Thread {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
private String name;
private String dateStr;
public DataFormatTest(String name, String dateStr) {
this.name = name;
this.dateStr = dateStr;
}
@Override
public void run() {
try {
Date date = sdf.parse(dateStr);
System.out.println(name + ": date:" + date);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(new DataFormatTest("A----->", "2022-02-01"));
// Thread.sleep(100);
executorService.execute(new DataFormatTest("B----->", "2022-02-31"));
executorService.shutdown();
}
}机器够好,每次运行都是错误:
java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890) at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110) at java.lang.Double.parseDouble(Double.java:538) at java.text.DigitList.getDouble(DigitList.java:169) at java.text.DecimalFormat.parse(DecimalFormat.java:2056) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) at com.test.DataFormatTest.run(DataFormatTest.java:23) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)
如果把休眠时间注释去掉,因为不再存在多线程并发问题,程序正常运行。
解决
1:将SimpleDateFormat定义成局部变量。
2:方法加同步锁synchronized,在同一时刻,只有一个线程可以执行类中的某个方法。
3:使用ThreadLocal:每个线程拥有自己的SimpleDateFormat对象。
4:使用DateTimeFormatter代替SimpleDateFormat。
DateTimeFormatter使用示例,多线程不会有问题
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DataFormatTest2 extends Thread {
private static DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
private static DateTimeFormatter dtf2=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private String name;
private String dateStr;
public DataFormatTest2(String name, String dateStr) {
this.name = name;
this.dateStr = dateStr;
}
@Override
public void run() {
try {
// 解析为日期
LocalDateTime localDateTime = LocalDateTime.parse(dateStr, dtf);
// 格式化日期
System.out.println(name + ": date:" + localDateTime.format(dtf));
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(new DataFormatTest2("A----->", "2022/02/02 15:23:46"));
executorService.execute(new DataFormatTest2("B----->", "2022/02/22 15:23:46"));
executorService.shutdown();
}
}初始化一个LocalDateTime对象
Obtains an instance of LocalDateTime from year, month, day, hour, minute, second and nanosecond.
This returns a LocalDateTime with the specified year, month, day-of-month, hour, minute, second and nanosecond. The day must be valid for the year and month, otherwise an exception will be thrown.
Params:
year – the year to represent, from MIN_YEAR to MAX_YEAR
month – the month-of-year to represent, from 1 (January) to 12 (December)
dayOfMonth – the day-of-month to represent, from 1 to 31
hour – the hour-of-day to represent, from 0 to 23
minute – the minute-of-hour to represent, from 0 to 59
second – the second-of-minute to represent, from 0 to 59
nanoOfSecond – the nano-of-second to represent, from 0 to 999,999,999
Returns:
the local date-time, not null
Throws:
DateTimeException – if the value of any field is out of range, or if the day-of-month is invalid for the month-year
public static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond) {
LocalDate date = LocalDate.of(year, month, dayOfMonth);
LocalTime time = LocalTime.of(hour, minute, second, nanoOfSecond);
return new LocalDateTime(date, time);
}Date和LocalDateTime对象互转
// Date转LocalDateTime
Date date = new Date();
LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
// LocalDateTime转Date
ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
date = Date.from(zdt.toInstant());
System.out.println(localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
END