在多线程环境下,当多个线程同时使用相同的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_YEARmonth – the month-of-year to represent, from 1 (January) to 12 (December)dayOfMonth – the day-of-month to represent, from 1 to 31hour – the hour-of-day to represent, from 0 to 23minute – the minute-of-hour to represent, from 0 to 59second – the second-of-minute to represent, from 0 to 59nanoOfSecond – the nano-of-second to represent, from 0 to 999,999,999Returns:the local date-time, not nullThrows:DateTimeException – if the value of any field is out of range, or if the day-of-month is invalid for the month-yearpublic 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转LocalDateTimeDate date = new Date();LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());// LocalDateTime转DateZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());date = Date.from(zdt.toInstant());System.out.println(localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
END