java日期格式化SimpleDateFormat的使用

一、SimpleDateFormat类格式化符号

日期和时间模式(注意大小写,代表的含义是不同的)
yyyy:年
MM:月
dd:日
hh:12小时制(0-11)
HH:24小时制(0-23)
mm:分
ss:秒
S:毫秒
E:星期几
D:一年中的第几天
F:一月中的第几个星期(会把这个月总共过的天数除以7)
w:一年中的第几个星期
W:一月中的第几星期(会根据实际情况来算)
a:上下午标识
k:和HH差不多,表示一天24小时制(1-24)。
K:和hh差不多,表示一天12小时制(0-11)。
z:表示时区

举个栗子:如果你想显示日期格式为 2022-07-15 13:00:00 000
使用格式就应该为 yyyy-MM-dd HH:mm:ss SSS(要注意小时数的大小写之分,用错了你就可能会突然发现你的时间少了十二小时:)

二、SimpleDateFormat类的具体使用

  1. Date 转String 可使用 format(Object obj),String转Date可使用parse(String source);

  2. 时间字串String转Date时,parse时如果时间字串与模式字串不匹配会报ParseException异常。

Date d = new Date();
        Date d = new Date();
        System.out.println(d);
        // 要转换的格式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        SimpleDateFormat sdf1 = new SimpleDateFormat(
                "一年中的第 D 天 一年中第w个星期 一月中第W个星期 E a 在一天中K时 z时区");
        // 格式化日期,日期->字符串
        String formatDate = sdf.format(d);
        String formatDate1 = sdf1.format(d);
        System.out.println(formatDate);
        System.out.println(formatDate1);

        String s = "2022-07-13 15:12:34 000";
        try {
            Date date = sdf.parse(s);//格式化日期,字符串->日期
            System.out.println(date);
        } catch (ParseException e) {
            e.printStackTrace();
        }

运行后

三、线程安全

  通过阅读源码我们可以发现,在他的代码中提及了Date formats are not synchronized.也就是该方法是线程不安全的。因为SimpleDateFormat 继承自 DateFormat,在 DateFormat 中定义了一个 protected 属性的 Calendar 类对象:calendar。因为 Calendar 类牵扯到了时区与本地化,JDK 的实现中使用了成员变量来传递参数,这就造成在多线程的时候会出现错误。

可以假设线程A执行完calendar.setTime(date),把时间设置成当前时间,这时候被挂起,线程B获得CPU执行权。线程B也执行到了calendar.setTime(date),把时间设置为新的当前时间。线程挂起,线程A继续走,calendar还会被继续使用(subFormat方法),而这时calendar用的是线程B设置的值了,出现时间不对,线程挂死问题等等。

解决方法

1.局部变量

  最简单的一种方式就是将 SimpleDateFormat 类对象定义成局部变量,也就是遇到需要时间日期字串转换或格式化的时候,再new 一个SimpleDateFormat类对象。不过这样做的缺点就是再高并发的情况下会创建很多对象,影响程序性能。

2.DateTimeFormatter 方式

  DateTimeFormatter 是 Java8 提供的新的日期时间 API 中的类,DateTimeFormatter 类是线程安全的,可以在高并发场景下直接使用 DateTimeFormatter 类来处理日期的格式化操作。代码如下所示:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class DateTimeFormatterTest {

    /** DateTimeFormatter 对象 */
    private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    /** 同时运行的线程数量 */
    private static final int THREAD_COUNT = 20;

    /** 执行总次数 */
    private static final int EXECUTE_COUNT = 1000;

    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(THREAD_COUNT);
        CountDownLatch latch = new CountDownLatch(EXECUTE_COUNT);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < EXECUTE_COUNT; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    LocalDate.parse("2022-06-07", DTF);
                } catch (InterruptedException e) {
                    System.out.println("获取信号量出错");
                    e.printStackTrace();
                    System.exit(1);
                } catch (Exception e) {
                    System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
                    e.printStackTrace();
                    System.exit(1);
                }
                semaphore.release();
                latch.countDown();
            });
        }
        latch.await();
        executorService.shutdown();
        System.out.println("所有线程格式化日期成功");
    }
}

3.使用ThreadLocal 方式

使用 ThreadLocal 存储每个线程拥有的 SimpleDateFormat 对象的副本,能够有效的避免多线程造成的线程安全问题,使用 ThreadLocal 解决线程安全问题的代码如下所示:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class SimpleDateFormatTest {

    /** 执行总次数 */
    private static final int EXECUTE_COUNT = 1000;

    /** 同时运行的线程数量 */
    private static final int THREAD_COUNT = 20;

    private static final ThreadLocal<SimpleDateFormat> THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(THREAD_COUNT);
        CountDownLatch latch = new CountDownLatch(EXECUTE_COUNT);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < EXECUTE_COUNT; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    SimpleDateFormat sdf = THREAD_LOCAL.get();
                    sdf.parse("2022-06-07");
                } catch (InterruptedException e) {
                    System.out.println("获取信号量出错");
                    e.printStackTrace();
                    System.exit(1);
                } catch (ParseException e) {
                    System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
                    e.printStackTrace();
                    System.exit(1);
                } catch (NumberFormatException e) {
                    System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
                    e.printStackTrace();
                    System.exit(1);
                }
                semaphore.release();
                latch.countDown();
            });
        }
        latch.await();
        executorService.shutdown();
        System.out.println("所有线程格式化日期成功");
    }
}

(关于线程问题解决方法参考:dh@SimpleDateFormat 类的线程安全问题

posted @ 2022-07-15 11:41  B1nbin  阅读(9232)  评论(0编辑  收藏  举报
/*
*/