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