一. SimpleDateFormat

想必大家对 SimpleDateFormat 并不陌生。SimpleDateFormat 是 Java 中一个非常常用的类,他是以区域敏感的方式格式化和解析日期的具体类。 它允许格式化 (date -> text)、语法分析 (text -> date)和标准化。

1. 日期时间格式(例如:yyyy-MM-dd HH:mm:ss)

java date 改成YYYY_MM_DD_线程安全

2. 实例说明(Date–>Text)

SimpleDateFormat myFmt = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
SimpleDateFormat myFmt1 = new SimpleDateFormat("yy/MM/dd HH:mm");
SimpleDateFormat myFmt2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//等价于now.toLocaleString()
SimpleDateFormat myFmt3 = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 E ");
SimpleDateFormat myFmt4 = new SimpleDateFormat("一年中的第 D 天 一年中第w个星期 一月中第W个星期 在一天中k时 z时区");
Date now = new Date();
System.out.println(myFmt.format(now));
System.out.println(myFmt1.format(now));
System.out.println(myFmt2.format(now));
System.out.println(myFmt3.format(now));
System.out.println(myFmt4.format(now));
System.out.println(now.toGMTString());
System.out.println(now.toLocaleString());
System.out.println(now.toString());

java date 改成YYYY_MM_DD_线程安全_02

3. 实例说明(Text–>Date)

String time1 = "2018年06月19日 23时10分05秒";
 String time2 = "18/06/19 23:10";
 String time3 = "2018-06-19 23:10:05";
 String time4 = "2018年06月19日 23时10分05秒 星期二";        SimpleDateFormat myFmt = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
 SimpleDateFormat myFmt1 = new SimpleDateFormat("yy/MM/dd HH:mm");
 SimpleDateFormat myFmt2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//等价于now.toLocaleString()
 SimpleDateFormat myFmt3 = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 E");
 Date date1 = null;
 try {
     date1 = myFmt.parse(time1);
 } catch (ParseException e) {
     e.printStackTrace();
 }
 System.out.println(date1);

 Date date2 = null;
 try {
     date2 = myFmt1.parse(time2);
 } catch (ParseException e) {
     e.printStackTrace();
 }
 System.out.println(date2);

 Date date3 = null;
 try {
     date3 = myFmt2.parse(time3);
 } catch (ParseException e) {
     e.printStackTrace();
 }
 System.out.println(date3);

 Date date4 = null;
 try {
     date4 = myFmt3.parse(time4);
 } catch (ParseException e) {
     e.printStackTrace();
 }
 System.out.println(date4);

结果:

java date 改成YYYY_MM_DD_java时间转化_03

4.线程不安全

多个线程同时对一个SimpleDateFormat对象进行操作的时候,就会出现错乱。

例如全局的private static final SimpleDateFormat df = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
 查看SimpleDateFormat 源码:calendar为protected类型的全局变量,当线程A设置了calendar.setTime(date);

线程B又设置了一次,那么calendar值就变了,线程A在subFormat方法中用到的calendar就不是自己设置的而是线程B设置的。

java date 改成YYYY_MM_DD_java时间转化_04


java date 改成YYYY_MM_DD_java时间转化_05


这个问题背后隐藏着一个更为重要的问题–无状态:无状态方法的好处之一,就是它在各种环境下,都可以安全的调用。衡量一个方法是否是有状态的,就看它是否改动了其它的东西,比如全局变量,比如实例的字段。format 方法在运行过程中改动了 SimpleDateFormat 的 calendar 字段,所以,它是有状态的。

5. 解决办法

1.每个线程使用的时候创建新的SimpleDateFormat,将有线程安全问题的对象由共享变为局部私有都能避免多线程问题
2.使用同步:同步 SimpleDateFormat 对象

public class DateSyncUtil {
		private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		public static String formatDate(Date date) throws ParseException {
		    synchronized (sdf) {
		        return sdf.format(date);
		    }
		}
		public static Date parse(String strDate) throws ParseException {
		    synchronized (sdf) {
		        return sdf.parse(strDate);
		    }
		}
}
说明:当线程较多时,当一个线程调用该方法时,其他想要调用此方法的线程就要 block 等待,多线程并发量大的时候会对性能有一定的影响

3.使用 ThreadLocal

public class DateSyncUtil {
        private ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){
            @Override
            protected DateFormat initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            }
        };
        public Date parse(String dateStr){
            return threadLocal.get().parse(dateStr);
        }
        public String format(Date date) {
            return threadLocal.get().format(date);
        }
 }
 说明:使用 ThreadLocal, 也是将共享变量变为独享,线程独享肯定能比方法独享在并发环境中能减少不少创建对象的开销。如果对性能要求比较高的情况下,一般推荐使用这种方法。

二. DateTimeFormatter

Java 8 提供了新的日期时间 API,其中包括用于日期时间格式化的 DateTimeFormatter,它与 SimpleDateFormat 最大的区别在于:DateTimeFormatter 是线程安全的,而 SimpleDateFormat 并不是线程安全。

//Text-->Date
String dateStr= "2018-06-20 11:25:56";
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.parse(dateStr, dateTimeFormatter);
ZoneId zone = ZoneId.systemDefault();
Instant instant = localDateTime.atZone(zone).toInstant();
Date date = Date.from(instant);

//Date-->Text
Date date = new Date();
Instant instant = date.toInstant();
ZoneId zone = ZoneId.systemDefault();
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String nowStr = localDateTime.format(format);
System.out.println(nowStr);

//LocalDateTime获取毫秒
System.out.println(localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli());