面试官问 : SimpleDateFormat 不是线程安全的,你有了解过吗?

前言

金三银四又有战况:


我们的看官,不能白白牺牲!

现在,立刻,马上,跟我开始复现 ! 开始看我源码分析! 开始了解怎么解决!

 正文

扫描二维码关注公众号,回复: 14623867 查看本文章

复现代码


多线程操作使用SimpleDateFormat ,来进行 时间格式转换, 将时间字符串格式 转换 成 Date 。

10个线程(量小模拟,多点几次才能出现并发情况)

开搞:

    public static void main(String[] args)  {
        
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
      
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                try {
                    String str1 = "2022-01-1" + new Random().nextInt(10);
                    Date result1 = simpleDateFormat.parse(str1);
                    System.out.println(Thread.currentThread().getName() + "时间str:  " + str1 + ", 转换对应的date :" + result1.toString());
                } catch (ParseException e) {
                    throw new RuntimeException(e);
                }
            });
            thread.start();
        }
    }

 一顿狂点, 看看线程不安全的 SimpleDateFormat 出来的丑态:
 

‘’不安全‘’的现场

 
 ① 报错了
 


 
 ② 出来了混乱的数据 (线程少,多点几次大并发的时候才能出来这些乱数据状况)

 

 

  为什么会这样?
 
 
其实线程不安全,万变不离其中,就是有公共资源没有隔离使用好,多线程操作的时候,公共资源错乱了。

 ok ,拒绝纸上谈兵,我们源码谈兵:

源码分析

simpleDateFormat.parse(str1) ,点进去看看源码:
  
  
  
  
 parse(source, pos)  ,点进去看看源码:
   

可以看到最后返回的是  Date parsedDate ,来至于 罪魁祸首  calb.establish(calendar) 

到这一步,其实敏感的人已经知道问题了。

因为 calendar  是 紫色的 

就是说 是个公共的资源,看一眼什么来头:
 

共用的:

 接着继续点calb.establish(calendar) ,看看源码:

 

共用的东西,不做线程安全隔离,自然就不安全了。

顺带看看我们也常用的 format函数:

同样也是用了公共的 calendar :

 

那怎么安全?

①上锁  
   
 每个线程都给我一一排队等锁来运行 parse()

评价: synchronized 加锁后的多线程=串行执行锁住的代码块,线程阻塞,执行效率低,不推荐。 


②一个线程一个SimpleDateFormat

 既然 我们代码用的simpleDateFormat 是共用的, 所以里面的calendar 是同一个共用的。
   
 我们要是不想让calendar是公共的,我们创建多个就行。
 所以每个线程,我们单独给它new 一个 simpleDateFormat  ,不就解决了么。

 

 评价: 一个线程new一个,一万个线程一万个,创建、销毁,资源消耗大,不推荐。


③ 使用ThreadLocal ,使用ThreadLocal存储每个线程拥有的SimpleDateFormat对象的副本,互相不干扰。

  评价: 不错。推荐!


④ 使用java 8 可以说是  专门 针对 这个 线程不安全的   SimpleDateFormat 搞的 DateTimeFormatter 来处理。


    private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {

                    String str1 = "2022-01-1" + new Random().nextInt(10);
                    LocalDate localDate = LocalDate.parse(str1, formatter);
                    Date result1 = Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
                    System.out.println(Thread.currentThread().getName() + "时间str:  " + str1 + ", 转换对应的date :" + result1.toString());

            });
            thread.start();
        }
    }

同样如果是需要date转字符串:

String dateStr = formatter.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(result1.getTime()), ZoneId.systemDefault()));


好了,该篇就到这。

猜你喜欢

转载自blog.csdn.net/qq_35387940/article/details/129897118