精华内容
下载资源
问答
  • 如果在单线程中,可以不用考虑使用的格式化类是否线程安全,但是在多线程,并发执行时,就要考虑线程同步的问题了。 下面提供四中解决方式,并简单说明一下优缺点(看注释)。 实际工作中我是直接使用 hutool 的 ...

    在项目中或多或少会用到日期格式。如果在单线程中,可以不用考虑使用的格式化类是否线程安全,但是在多线程,并发执行时,就要考虑线程同步的问题了。
    下面提供四中解决方式,并简单说明一下优缺点(看注释)。
    实际工作中我是直接使用 hutoolDateUtil,真香。

    ConcurrentDateFormat 和 ThreadLocalDateFormat 是自己封装的

    import org.junit.Test;
    
    import java.text.SimpleDateFormat;
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 验证 SimpleDateFormat 是线程不安全的
     */
    public class DateFormat {
    
        /**
         * 只创建一份,避免频繁地创建对象和销毁对象
         * 单线程下可以不出错
         * 多线程下则不安全, 不同的线程会对不同日期字符串进行解析,
         * 会出现线程 A 解析到一半被挂起,线程 B 运行时将 A 的解析到一半的字符串覆盖掉,
         * 这样轮到 A 运行时会解析失败,或者使用了 B 的字符串
         */
        private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        /**
         * 线程安全的 DateTimeFormatter
         * 推荐使用,因为该类是不可变的,并且是线程安全的
         */
        private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    
        @Test
        public void formatTest() {
            ExecutorService service = Executors.newFixedThreadPool(10);
    
            for(int i = 0; i < 10; i++){
                service.execute(new Runnable() {
                    @Override
                    public void run() {
                        String dateStr = "2019-04-16 10:26:30";
                        // 解决方法
                        // 1、可以只在需要时创建对象,也可以避免错误,但是频繁创建与销毁会导致额外的开销,性能低
    //                    SimpleDateFormat newInNeed = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        for(int j = 0; j < 10; j++){
    //
    //                        try {
                                // 直接使用不但运行结果错误,最后还会抛出 NumberFormatException: 异常
    //                            System.out.println(format.parse(dateStr));
    
                                // 2、使用加了 synchronized 的同步方法,但是并发量高时,性能影响大,线程阻塞
    //                            System.out.println(ConcurrentDateFormat.parse(dateStr));
    
                                // 3、使用 ThreadLocal 来解决,比较优雅的一种做法
    //                            System.out.println(ThreadLocalDateFormat.parse(dateStr));
    
    //                        } catch (ParseException e) {
    //                            e.printStackTrace();
    //                        }
                            // 4、使用DateFormatter,该类是线程安全的,可以放心使用
                            LocalDateTime localDateTime = LocalDateTime.parse(dateStr, dtf);
                            System.out.println(localDateTime);
                        }
                    }
                });
            }
        }
    }
    

    ConcurrentDateFormat.java

    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * 通过 synchronized 做一个线程安全的DataFormat
     */
    public class ConcurrentDateFormat {
        /**
         * 依旧只创建一个 SimpleFormat 对象
         */
        private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        // 采用加锁的方式,防止同步问题
        public static String format(Date date){
            synchronized (sdf){
                return sdf.format(date);
            }
        }
    
        public static Date parse(String date) throws ParseException {
            synchronized (sdf){
                return sdf.parse(date);
            }
        }
    
    }
    

    ThreadLocalDateFormat.java

    import java.text.DateFormat;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * 通过 ThreadLocal 为每个线程做一份变量副本,实现线程安全
     */
    public class ThreadLocalDateFormat {
    
        /**
         * ThreadLocal 提供一种 lombda 构造方式
         * 返回此线程局部变量的当前线程的“初始值”。线程第一次使用 get() 方法访问变量时将调用此方法,
         * 但如果线程之前调用了 set(T) 方法,则不会对该线程再调用 initialValue 方法。通常,此方法
         * 对每个线程最多调用一次,但如果在调用 get() 后又调用了 remove(),则可能再次调用此方法。
         */
        private static ThreadLocal<DateFormat> threadLocal
                = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    
        public static Date parse(String date) throws ParseException {
            System.out.println(date);
            return threadLocal.get().parse(date);
        }
    
        public static String format(Date date){
            return threadLocal.get().format(date);
        }
    }
    
    展开全文
  • 需求:让多线程同时去解析日期 错误示范 public class Test1 { @Test public void test01() throws Exception { //格式化日期类 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); //任务类:用于...
  • 如果在单线程中,可以不用考虑使用的格式化类是否线程安全,但是在多线程,并发执行时,就要考虑线程同步的问题了。下面提供四中解决方式,并简单说明一下优缺点(看注释)ConcurrentDateFormat 和 ...

    在项目中或多或少会用到日期格式。如果在单线程中,可以不用考虑使用的格式化类是否线程安全,但是在多线程,并发执行时,就要考虑线程同步的问题了。

    下面提供四中解决方式,并简单说明一下优缺点(看注释)

    ConcurrentDateFormat 和 ThreadLocalDateFormat 是自己封装的

    import org.junit.Test;

    import java.text.SimpleDateFormat;

    import java.time.LocalDateTime;

    import java.time.format.DateTimeFormatter;

    import java.util.concurrent.ExecutorService;

    import java.util.concurrent.Executors;

    /**

    * 验证 SimpleDateFormat 是线程不安全的

    */

    public class DateFormat {

    /**

    * 只创建一份,避免频繁地创建对象和销毁对象

    * 单线程下可以不出错

    * 多线程下则不安全, 不同的线程会对不同日期字符串进行解析,

    * 会出现线程 A 解析到一半被挂起,线程 B 运行时将 A 的解析到一半的字符串覆盖掉,

    * 这样轮到 A 运行时会解析失败,或者使用了 B 的字符串

    */

    private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**

    * 线程安全的 DateTimeFormatter

    * 推荐使用,因为该类是不可变的,并且是线程安全的

    */

    private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Test

    public void formatTest() {

    ExecutorService service = Executors.newFixedThreadPool(10);

    for(int i = 0; i < 10; i++){

    service.execute(new Runnable() {

    @Override

    public void run() {

    String dateStr = "2019-04-16 10:26:30";

    // 解决方法

    // 1、可以只在需要时创建对象,也可以避免错误,但是频繁创建与销毁会导致额外的开销,性能低

    // SimpleDateFormat newInNeed = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    for(int j = 0; j < 10; j++){

    //

    // try {

    // 直接使用不但运行结果错误,最后还会抛出 NumberFormatException: 异常

    // System.out.println(format.parse(dateStr));

    // 2、使用加了 synchronized 的同步方法,但是并发量高时,性能影响大,线程阻塞

    // System.out.println(ConcurrentDateFormat.parse(dateStr));

    // 3、使用 ThreadLocal 来解决,比较优雅的一种做法

    // System.out.println(ThreadLocalDateFormat.parse(dateStr));

    // } catch (ParseException e) {

    // e.printStackTrace();

    // }

    // 4、使用DateFormatter,该类是线程安全的,可以放心使用

    LocalDateTime localDateTime = LocalDateTime.parse(dateStr, dtf);

    System.out.println(localDateTime);

    }

    }

    });

    }

    }

    }

    ConcurrentDateFormat.java

    import java.text.ParseException;

    import java.text.SimpleDateFormat;

    import java.util.Date;

    /**

    * 通过 synchronized 做一个线程安全的DataFormat

    */

    public class ConcurrentDateFormat {

    /**

    * 依旧只创建一个 SimpleFormat 对象

    */

    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    // 采用加锁的方式,防止同步问题

    public static String format(Date date){

    synchronized (sdf){

    return sdf.format(date);

    }

    }

    public static Date parse(String date) throws ParseException {

    synchronized (sdf){

    return sdf.parse(date);

    }

    }

    }

    ThreadLocalDateFormat.java

    import java.text.DateFormat;

    import java.text.ParseException;

    import java.text.SimpleDateFormat;

    import java.util.Date;

    /**

    * 通过 ThreadLocal 为每个线程做一份变量副本,实现线程安全

    */

    public class ThreadLocalDateFormat {

    /**

    * ThreadLocal 提供一种 lombda 构造方式

    * 返回此线程局部变量的当前线程的“初始值”。线程第一次使用 get() 方法访问变量时将调用此方法,

    * 但如果线程之前调用了 set(T) 方法,则不会对该线程再调用 initialValue 方法。通常,此方法

    * 对每个线程最多调用一次,但如果在调用 get() 后又调用了 remove(),则可能再次调用此方法。

    */

    private static ThreadLocal threadLocal

    = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static Date parse(String date) throws ParseException {

    System.out.println(date);

    return threadLocal.get().parse(date);

    }

    public static String format(Date date){

    return threadLocal.get().format(date);

    }

    }

    原文:https://blog.csdn.net/innocent_jia/article/details/89331045

    展开全文
  • 首先格式化可以有很种格式 SimpleDateformat("yyyy-MM-dd HH:mm:ss"); SimpleDateformat("yyyyMMddHHmmss");//年月日小时分钟秒 1、SimpleDateFormat是非线程安全的类,所以不能直接...

    首先格式化可以有很多种格式

    SimpleDateformat("yyyy-MM-dd HH:mm:ss");
    SimpleDateformat("yyyyMMddHHmmss");//年月日小时分钟秒

    1、SimpleDateFormat是非线程安全的类,所以不能直接修饰为static。这会导致发生线程安全问题。

    这个类在执行的时候是通过一个calendar变量来保存时间的,假如你定义为static的时候,那么calendar也将是共享的变量,那么多线程访问的时候,比如对时间进行增加,线程1加到为12月5号,这个时候线程1还没有执行完就被线程2执行了,线程2是加到为12月6号,线程2执行完后才轮到线程1继续执行,这个时候就会发现你本意是通过线程加到12月6号的,结果发现执行完确实5号。就是产生了线程安全问题。

    那么怎么解决呢?

    第一:就是不把Simpleformat作为全局变量,弄成局部变量

    第二:加线程安全锁

    第三:使用ThreadLocal,ThreadLocal 可以确保每个线程都可以得到单独的一个 SimpleDateFormat 的对象,也就不会出现那种线程抢占导致的问题了

    /**
    * 使用ThreadLocal定义一个全局的SimpleDateFormat
    */
    private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
       @Override
       protected SimpleDateFormat initialValue() {
           return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
       }
    };
    
    //用法
    String dateString = simpleDateFormatThreadLocal.get().format(calendar.getTime());

     

    展开全文
  • SimpleDateFormat 是 JDK 提供的一个日期格式化和解析类,但它是非线程安全的,原因如下。 1. parse方法 Date parsedDate; try { parsedDate = calb.establish(calendar).getTime(); // If the year v...

    前言

    SimpleDateFormat 是 JDK 提供的一个日期格式化和解析类,但它是非线程安全的,原因如下。

    1. parse方法

            Date parsedDate;
            try {
                parsedDate = calb.establish(calendar).getTime();
                // If the year value is ambiguous,
                // then the two-digit year == the default start year
                if (ambiguousYear[0]) {
                    if (parsedDate.before(defaultCenturyStart)) {
                        parsedDate = calb.addYear(100).establish(calendar).getTime();
                    }
                }
            }
            // An IllegalArgumentException will be thrown by Calendar.getTime()
            // if any fields are out of range, e.g., MONTH == 17.
            catch (IllegalArgumentException e) {
                pos.errorIndex = start;
                pos.index = oldStart;
                return null;
            }

    其中,calb.establish(calendar)初始化了calendar日历数据

    跟进查看

            //1-----------------------清空
            cal.clear();
            // Set the fields from the min stamp to the max stamp so that
            // the field resolution works in the Calendar.
            for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
                for (int index = 0; index <= maxFieldIndex; index++) {
                    if (field[index] == stamp) {
                        cal.set(index, field[MAX_FIELD + index]);//2--------------设置
                        break;
                    }
                }
            }
    
            if (weekDate) {
                int weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1;
                int dayOfWeek = isSet(DAY_OF_WEEK) ?
                                    field[MAX_FIELD + DAY_OF_WEEK] : cal.getFirstDayOfWeek();
                if (!isValidDayOfWeek(dayOfWeek) && cal.isLenient()) {
                    if (dayOfWeek >= 8) {
                        dayOfWeek--;
                        weekOfYear += dayOfWeek / 7;
                        dayOfWeek = (dayOfWeek % 7) + 1;
                    } else {
                        while (dayOfWeek <= 0) {
                            dayOfWeek += 7;
                            weekOfYear--;
                        }
                    }
                    dayOfWeek = toCalendarDayOfWeek(dayOfWeek);
                }
                cal.setWeekDate(field[MAX_FIELD + WEEK_YEAR], weekOfYear, dayOfWeek);//2-----------设置
            }
    
            //3-------------------返回
            return cal;

    参考1- 2- 3-注释,线程不安全的原因,不能保证多核模式下,多线程执行是顺序执行的原子性

    2. format方法源码

    /**
         * Formats the given <code>Date</code> into a date/time string and appends
         * the result to the given <code>StringBuffer</code>.
         *
         * @param date the date-time value to be formatted into a date-time string.
         * @param toAppendTo where the new date-time text is to be appended.
         * @param pos the formatting position. On input: an alignment field,
         * if desired. On output: the offsets of the alignment field.
         * @return the formatted date-time string.
         * @exception NullPointerException if the given {@code date} is {@code null}.
         */
        @Override
        public StringBuffer format(Date date, StringBuffer toAppendTo,
                                   FieldPosition pos)
        {
            pos.beginIndex = pos.endIndex = 0;
            return format(date, toAppendTo, pos.getFieldDelegate());
        }
    
        // Called from Format after creating a FieldDelegate
        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;
        }

    //设置日期

    calendar.setTime(date); 

    //格式化时间

    subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);

    calendar是有状态的,不能保证原子性。

    3. 解决方法

    3.1. 加锁

    自建方法包裹代码

    
    public class SimpleDateFormatTest {
    
        
        static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        public static synchronized Date parse(String s) throws ParseException {
            return sdf.parse(s);
        }
    
        public static void main(String[] args) {
            
            for (int i = 0; i <20 ; ++i) {
                Thread thread = new Thread(() -> {
                    public void run() {
                        try {
                            
                                System.out.println(parse("2018-07-17 10:52:16"));
                            
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                    }
                };
                thread.start()
            }
        }
    
    }

    串行执行,在大批量调用效率很低,不推荐

    3.2.每次调用创建一个对象

        public Date parse(String s) throws ParseException {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            return sdf.parse(s);
        }

    缺点,耗内存,GC压力增大,每次创建对象销毁,分配和销毁内存空间,整理内存,GC。不推荐

    3.3.每个线程创建一个对象

    每个线程带一个ThreadLocalMap对象,以threadlocal对象为key,传递变量为值。每个线程仅修改当前线程的副本。

    public class SimpleDateFormatTest {
        
        static ThreadLocal<SimpleDateFormat> local= new ThreadLocal<SimpleDateFormat>(){
            @Override
            protected SimpleDateFormat initialValue(){
                return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            }
        };
    
        public static void main(String[] args) {
            
            for (int i = 0; i < 5; ++i) {
                Thread thread = new Thread(() -> {
                    public void run() {
                        try {
                            System.out.println(local.get().parse("2018-07-17 11:01:16"));
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }finally {
                            //TODO 线程运行结束清除,避免内存泄露
                            local.remove();
                        }
                    }
                });
    
                thread.start();
            }
        }
    
    }

    4. 总结 

    推荐使用ThreadLocal,每个线程一个对象,提高format对象利用率

    注意:使用ThreadLocal的Thread均持有ThreadLocal自定义的map对象,key是ThreadLocal对象的弱引用,随ThreadLocal的GC或者内存不足就会GC,value是我们创建的对象是强引用,可能一直存在。因此ThreadLocal调用结束后,需要显示调用remove()方法

    展开全文
  • Java8之前的日期和时间API,存在一些问题,最重要的就是线程安全的问题。...传统时间格式化线程安全问题示例:importjava.text.SimpleDateFormat;importjava.util.ArrayList;importjava.util.Date;importjava.util...
  • 那么在多线程中,就会出现错误的结果如下代码: public static void main(String[] args){ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); Callable&lt;Date&gt; callable = new...
  • 如果我们想要使用SimpleDateFormat类来对一个时间或者日期进行格式化,并且还要使用多线程来操作,即使用多线程同时对一个时间或者日期进行格式化,那么该咋办呢?我们可以创建一个线程池,然后分10次去访问定义好的...
  • 由于 DateFormat 是非线程安全的,因此在多线程并发情况下日期格式化时需要特别注意。下面记录几种格式化的方式:线程不安全的处理方式private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-...
  • 由于 DateFormat 是非线程安全的,因此在多线程并发情况下日期格式化时需要特别注意。下面记录几种格式化的方式: 线程不安全的处理方式 private static final DateFormat DATE_FORMAT = new SimpleDateForma.....
  • } SimpleDateFormat:格式化日期类,在java.text 包下面,这个包是国际化都放在这个里面 这个类继承了DateFormat format(Date date) 将日期格式化转化为字符串 parse(String source) 将字符串转化为日期 ...
  • /**一个类中要访问线程,同时又用到格式化日期类时, 格式化日期类对象不能同时访问线程 *访问线程格式化日期类会出现意想不到的格式化错误,一个线程只能对应一个格式化日期类 * author :wuxq ...
  • 线程安全日期格式化操作的几种方式由于 DateFormat 是非线程安全的,因此在多线程并发情况下日期格式化时需要特别注意。下面记录几种格式化的方式:线程不安全的处理方式private static final DateFormat DATE_...
  • 由于 DateFormat 是非线程安全的,因此在多线程并发情况下日期格式化时需要特别注意。下面记录几种格式化的方式: 线程不安全的处理方式 private static final DateFormat DATE_FORMAT = new ...
  • 1、Java8之前日期的痛点1、构造年的日期需传入year-19002、每周的日期从周一开始,不符合各国用户习惯3、 TimeZone提供时区支持但不好用,且线程不安全4、日期格式化规划混乱,不应该在java.text包中2、Java8中的...
  • Java 中一个非常常用的类,该类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微妙和难以调试的问题,因为 DateFormat 和 SimpleDateFormat 类不都是线程安全的,在多线程环境下调用 ...
  • Java 中一个非常常用的类,该类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微妙和难以调试的问题,因为 DateFormat 和 SimpleDateFormat 类不都是线程安全的,在多线程环境下调用 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 490
精华内容 196
关键字:

日期格式化多线程