Java小强个人技术博客站点    手机版
当前位置: 首页 >> Java >> Java之SimpleDateFormat

Java之SimpleDateFormat

24780 Java | 2022-2-11

在多线程环境下,当多个线程同时使用相同的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

推荐您阅读更多有关于“ SimpleDateFormat ThreadLocal 线程安全 LocalDateTime ”的文章

上一篇:静态代码块、构造代码块、构造方法的执行顺序 下一篇:Java之LinkedHashMap

猜你喜欢

发表评论: