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);
