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