• 日期时间格式化 SimpleDateFormat与DateTimeFormatter


    原文:https://www.jianshu.com/p/b212afa16f1f

    1.SimpleDateFormat为什么不是线程安全的?

    • 如果我们把SimpleDateFormat定义成static成员变量,那么多个thread之间会共享这个SimpleDateFormat对象, 所以Calendar对象也会共享。
    public static SimpleDateFormat formater = new SimpleDateFormat(
                "yyyy-MM-dd HH:mm:ss");
    
    System.out.println(formater.format(new Date())+" Exception made...");
    
    • DateFormat.java
        public final String format(Date date)
        {
            return format(date, new StringBuffer(),
                          DontCareFieldPosition.INSTANCE).toString();
        }
    
        public abstract StringBuffer format(Date date, StringBuffer toAppendTo,
                                            FieldPosition fieldPosition);
    
    • SimpleDateFormat.java
        @Override
        public StringBuffer format(Date date, StringBuffer toAppendTo,
                                   FieldPosition pos)
        {
        //  如此轻易地使用内部变量,肯定不能线程安全
        //  线程都对pos进行写操作,必然会影响其他线程的读操作
            pos.beginIndex = pos.endIndex = 0;
            return format(date, toAppendTo, pos.getFieldDelegate());
        }
    
        private StringBuffer format(Date date, StringBuffer toAppendTo,
                                    FieldDelegate delegate) {
            // Convert input date to time field list
           // 这里已经彻底毁坏线程的安全性
            calendar.setTime(date);
    
            boolean useDateFormatSymbols = useDateFormatSymbols();
    
            for (int i = 0; i < compiledPattern.length; ) {
                int tag = compiledPattern[i] >>> 8;
                int count = compiledPattern[i++] & 0xff;
                if (count == 255) {
                    count = compiledPattern[i++] << 16;
                    count |= compiledPattern[i++];
                }
    
                switch (tag) {
                case TAG_QUOTE_ASCII_CHAR:
                    toAppendTo.append((char)count);
                    break;
    
                case TAG_QUOTE_CHARS:
                    toAppendTo.append(compiledPattern, i, count);
                    i += count;
                    break;
    
                default:
                    subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
                    break;
                }
            }
            return toAppendTo;
        }
    

    1.1 复现错误

    代码参考

    public class DateFormatTest {
        private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
        private static String date[] = { "01-Jan-1999", "09-Jan-2000", "08-Jan-2001" , "07-Jan-2002" , "06-Jan-2003" , "05-Jan-2004" , "04-Jan-2005" , "03-Jan-2006" , "02-Jan-2007" };
    
        public static void main(String[] args) {
            for (int i = 0; i < date.length; i++) {
                final int temp = i;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            while (true) {
                                String str1 = date[temp];
                                String str2 = sdf.format(sdf.parse(str1));
                                System.out.println(Thread.currentThread().getName() + ", " + str1 + "," + str2);
                                if(!str1.equals(str2)){
                                    throw new RuntimeException(Thread.currentThread().getName()
                                            + ", Expected " + str1 + " but got " + str2);
                                }
                            }
                        } catch (Exception e) {
                            throw new RuntimeException("parse failed", e);
                        }
                    }
                }).start();
            }
        }
    }
    
     
     

    2.SimpleDateFormat线程不安全的解决方法

    2.1 将SimpleDateFormat定义成局部变量:

    SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
    String str1 = "01-Jan-2010";
    String str2 = sdf.format(sdf.parse(str1));
    

    缺点:每调用一次方法就会创建一个SimpleDateFormat对象,方法结束又要作为垃圾回收。

    2.2 加一把线程同步锁:synchronized(lock)

    public class SyncDateFormatTest {
        private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
        private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" };
     
        public static void main(String[] args) {
            for (int i = 0; i < date.length; i++) {
                final int temp = i;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            while (true) {
                                synchronized (sdf) {
                                    String str1 = date[temp];
                                    Date date = sdf.parse(str1);
                                    String str2 = sdf.format(date);
                                    System.out.println(Thread.currentThread().getName() + ", " + str1 + "," + str2);
                                    if(!str1.equals(str2)){
                                        throw new RuntimeException(Thread.currentThread().getName() 
                                                + ", Expected " + str1 + " but got " + str2);
                                    }
                                }
                            }
                        } catch (Exception e) {
                            throw new RuntimeException("parse failed", e);
                        }
                    }
                }).start();
            }
        }
    }
    
    

    缺点:性能较差,每次都要等待锁释放后其他线程才能进入

    2.3 使用ThreadLocal

    每个线程都将拥有自己的SimpleDateFormat对象副本。

    public class DateUtil {
        private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<SimpleDateFormat>();
     
        public static Date parse(String str) throws Exception {
            SimpleDateFormat sdf = local.get();
            if (sdf == null) {
                sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
                local.set(sdf);
            }
            return sdf.parse(str);
        }
        
        public static String format(Date date) throws Exception {
            SimpleDateFormat sdf = local.get();
            if (sdf == null) {
                sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
                local.set(sdf);
            }
            return sdf.format(date);
        }
    }
    
    public class ThreadLocalDateFormatTest {
        private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" };
     
        public static void main(String[] args) {
            for (int i = 0; i < date.length; i++) {
                final int temp = i;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            while (true) {
                                String str1 = date[temp];
                                Date date = DateUtil.parse(str1);
                                String str2 = DateUtil.format(date);
                                System.out.println(str1 + "," + str2);
                                if(!str1.equals(str2)){
                                    throw new RuntimeException(Thread.currentThread().getName() 
                                            + ", Expected " + str1 + " but got " + str2);
                                }
                            }
                        } catch (Exception e) {
                            throw new RuntimeException("parse failed", e);
                        }
                    }
                }).start();
            }
        }
    }
    

    3.使用DateTimeFormatter代替SimpleDateFormat

    代码参考DateTimeFormatterTest.java
    jdk1.8中新增了 LocalDate 与 LocalDateTime等类来解决日期处理方法,同时引入了一个新的类DateTimeFormatter来解决日期格式化问题。
    LocalDateTime,DateTimeFormatter两个类都没有线程问题,只要你自己不把它们创建为共享变量就没有线程问题。
    可以使用Instant代替 Date,LocalDateTime代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MM dd");
    LocalDate date = LocalDate.parse("2017 06 17", formatter);
    System.out.println(formatter.format(date));
    

     自测类

    import java.text.SimpleDateFormat;
    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    import java.util.Locale;
    
    public class DateFormatTest {
        private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy", Locale.US);
        private static String date[] = { "01-01-1999", "09-01-2000", "08-01-2001" , "07-01-2002" , "06-01-2003" , "05-01-2004" , "04-01-2005" , "03-01-2006" , "02-01-2007" };
        
        private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
        
    
        public static void main(String[] args) {
            for (int i = 0; i < date.length; i++) {
                final int temp = i;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            while (true) {
                                String str1 = date[temp];
                                
                                //线程安全
                                LocalDate date = LocalDate.parse(str1, formatter);
                                String str2 =  formatter.format(date);
                                
                                //线程不安全
    //                            String str2 = sdf.format(sdf.parse(str1));
                                
                                System.out.println(Thread.currentThread().getName() + ", " + str1 + "," + str2);
                                if(!str1.equals(str2)){
                                    throw new RuntimeException(Thread.currentThread().getName()
                                            + ", Expected " + str1 + " but got " + str2);
                                }
                            }
                        } catch (Exception e) {
                            throw new RuntimeException("parse failed", e);
                        }
                    }
                }).start();
            }
        }
    }
     

     
  • 相关阅读:
    托管资源和非托管资源
    无法启动IIS EXpress Web服务器
    SQL 最后一天及第一天
    amchart amline中配置文件amline_settings.xml文件中的配置说明
    SpringBoot添加拦截器
    Lombok插件
    SpringBoot配置访问静态资源
    SpringBoot自动配置原理
    yaml基本格式
    属性文件之SpringBoot注入
  • 原文地址:https://www.cnblogs.com/shihaiming/p/11082154.html
Copyright © 2020-2023  润新知