SimpleDateFormat是線程不安全的類,一般不要定義成static變量。


package com.kris;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.*;

/**
 * @Author kris
 * @Discription todo
 * @Date 2020/7/23
 * @Classname DateFormat
 * @Version
 **/
public class DateFormat {
        /**     * 定義一個全局的 SimpleDateFormat     */
        private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss");
        /**     * 使用 ThreadFactoryBuilder 定義一個線程池     */
        private static ExecutorService pool = new ThreadPoolExecutor(10, 10,        60L, TimeUnit.SECONDS,        new ArrayBlockingQueue(100));
        /**     * 定義一個 CountDownLatch,保證所有子線程執行完之后主線程再執行     */
        private static CountDownLatch countDownLatch = new CountDownLatch(100);

        public static void main(String[] args) throws Exception{
            // 定義一個線程安全的 HashSet
            Set<String> dates = Collections.synchronizedSet(new HashSet<String>());
            for (int i = 0; i < 100; i++) {

                // 獲取當前時間
                Calendar calendar = Calendar.getInstance();
                int finalI = i;
                pool.execute(() -> {
                    // 時間增加
                    calendar.add(Calendar.DATE, finalI);
                    //通過 simpleDateFormat 把時間轉換成字符串
                    String dateString = simpleDateFormat.format(calendar. getTime());
                    // 把字符串放入 Set 中
                    dates.add(dateString);
                    //countDown
                    countDownLatch.countDown();
                });
            }
            // 阻塞,直到 countDown 數量為 0
            countDownLatch.await();
            // 輸出去重后的時間個數
            System.out.println(dates.size());
            System.out.println(dates.toString());
        }
}

 

結果

87
[2020-10-03 13:31:38, 2020-09-07 13:31:38, 2020-08-07 13:31:38, 2020-09-29 13:31:38, 2020-09-10 13:31:38, 2020-08-29 13:31:38, 2020-08-01 13:31:38, 2020-07-29 13:31:38, 2020-09-04 13:31:38, 2020-09-13 13:31:38, 2020-10-19 13:31:38, 2020-09-01 13:31:38, 2020-09-05 13:31:38, 2020-10-04 13:31:38, 2020-10-10 13:31:38, 2020-09-28 13:31:38, 2020-10-16 13:31:38, 2020-10-27 13:31:38, 2020-08-17 13:31:38, 2020-08-16 13:31:38, 2020-09-22 13:31:38, 2020-10-22 13:31:38, 2020-08-22 13:31:38, 2020-09-16 13:31:38, 2020-08-23 13:31:38, 2020-07-23 13:31:38, 2020-09-23 13:31:38, 2020-09-17 13:31:38, 2020-08-11 13:31:38, 2020-09-11 13:31:38, 2020-10-09 13:31:38, 2020-10-21 13:31:38, 2020-08-28 13:31:38, 2020-08-05 13:31:38, 2020-10-15 13:31:38, 2020-08-18 13:31:38, 2020-10-23 13:31:38, 2020-08-15 13:31:38, 2020-09-21 13:31:38, 2020-10-20 13:31:38, 2020-09-24 13:31:38, 2020-08-21 13:31:38, 2020-09-15 13:31:38, 2020-10-14 13:31:38, 2020-10-29 13:31:38, 2020-08-03 13:31:38, 2020-07-27 13:31:38, 2020-09-06 13:31:38, 2020-10-08 13:31:38, 2020-10-17 13:31:38, 2020-09-12 13:31:38, 2020-10-26 13:31:38, 2020-09-03 13:31:38, 2020-08-30 13:31:38, 2020-10-05 13:31:38, 2020-09-30 13:31:38, 2020-08-27 13:31:38, 2020-10-02 13:31:38, 2020-09-27 13:31:38, 2020-08-24 13:31:38, 2020-09-18 13:31:38, 2020-10-11 13:31:38, 2020-07-30 13:31:38, 2020-08-09 13:31:38, 2020-09-02 13:31:38, 2020-07-25 13:31:38, 2020-08-31 13:31:38, 2020-09-08 13:31:38, 2020-10-07 13:31:38, 2020-08-25 13:31:38, 2020-07-31 13:31:38, 2020-10-01 13:31:38, 2020-09-25 13:31:38, 2020-08-13 13:31:38, 2020-10-13 13:31:38, 2020-08-20 13:31:38, 2020-10-12 13:31:38, 2020-08-26 13:31:38, 2020-09-26 13:31:38, 2020-10-18 13:31:38, 2020-09-20 13:31:38, 2020-08-19 13:31:38, 2020-09-19 13:31:38, 2020-09-14 13:31:38, 2020-08-14 13:31:38, 2020-10-06 13:31:38, 2020-10-30 13:31:38]

 

SimpleDateFormat 中的 format 方法在執行過程中,會使用一個成員變量
calendar 來保存時間。這其實就是問題的關鍵。
由於我們在聲明 SimpleDateFormat 的時候,使用的是 static 定義的。那么
這個 SimpleDateFormat 就是一個共享變量,隨之,SimpleDateFormat 中的
calendar 也就可以被多個線程訪問到。
假設線程1剛剛執行完calendar.setTime 把時間設置成 2018-11-11,還
沒等執行完,線程2又執行了calendar.setTime 把時間改成了 2018-12-12。
這時候線程 1 繼續往下執行,拿到的 calendar.getTime 得到的時間就是線程 2 改
過之后的。
除了 format 方法以外,SimpleDateFormat 的 parse 方法也有同樣的問題。
所以,不要把 SimpleDateFormat 作為一個共享變量使用

解決方案

1,方案一

pool.execute(() -> {
                   //simpleDateFormat在這里設置成局部變量
                    // 時間增加
                    calendar.add(Calendar.DATE, finalI);
                    //通過 simpleDateFormat 把時間轉換成字符串
                    String dateString = simpleDateFormat.format(calendar. getTime());
                    // 把字符串放入 Set 中
                    dates.add(dateString);
                    //countDown
                    countDownLatch.countDown();
});

 2,方案二 給simpleDateFormat加鎖

pool.execute(() -> {
                    synchronized (simpleDateFormat){
                        // 時間增加
                        calendar.add(Calendar.DATE, finalI);
                        //通過 simpleDateFormat 把時間轉換成字符串
                        String dateString = simpleDateFormat.format(calendar. getTime());
                        // 把字符串放入 Set 中
                        dates.add(dateString);
                        //countDown
                        countDownLatch.countDown();
                    }
                });

 3,方案三 使用ThreadLocal

/** * 使用 ThreadLocal 定義一個全局的 SimpleDateFormat */
        private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
            @Override
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            }
        };

 使用的時候

pool.execute(() -> {
                        // 時間增加
                        calendar.add(Calendar.DATE, finalI);
                        //通過 simpleDateFormat 把時間轉換成字符串
                        //String dateString = simpleDateFormat.format(calendar. getTime());
                        String dateString = simpleDateFormatThreadLocal.get().format(calendar.getTime());
                        // 把字符串放入 Set 中
                        dates.add(dateString);
                        //countDown
                        countDownLatch.countDown();
                });

 4使用 DateTimeFormatter
如果是 Java8 應用,可以使用 DateTimeFormatter 代替 SimpleDateFormat,這是一個線程安全的格式化工具類。
// 解析日期

String dateStr= "2016 年 10 月 25 日 "; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
 LocalDate date= LocalDate.parse(dateStr, formatter);

 // 日期轉換為字符串

LocalDateTime now = LocalDateTime.now(); 
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy 年MM 月dd 日 hh:mm a");
String nowStr = now .format(format);

 

 

   

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM