成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

還在使用SimpleDateFormat?你的項目崩沒?

開發(fā) 后端
在多線程情況下,會出現(xiàn)異常,想必有經(jīng)驗的小伙伴也遇到過。下面我們就來分析分析SimpleDateFormat為什么不安全?是怎么引發(fā)的?以及多線程下有那些SimpleDateFormat的解決方案?

[[315752]]

一.前言

日常開發(fā)中,我們經(jīng)常需要使用時間相關(guān)類,說到時間相關(guān)類,想必大家對SimpleDateFormat并不陌生。主要是用它進(jìn)行時間的格式化輸出和解析,挺方便快捷的,但是SimpleDateFormat并不是一個線程安全的類。在多線程情況下,會出現(xiàn)異常,想必有經(jīng)驗的小伙伴也遇到過。下面我們就來分析分析SimpleDateFormat為什么不安全?是怎么引發(fā)的?以及多線程下有那些SimpleDateFormat的解決方案?

先看看《阿里巴巴開發(fā)手冊》對于SimpleDateFormat是怎么看待的:

公眾號后臺回復(fù)"阿里巴巴開發(fā)手冊"獲取《阿里巴巴開發(fā)手冊》v 1.4.0

二.問題場景復(fù)現(xiàn)

一般我們使用SimpleDateFormat的時候會把它定義為一個靜態(tài)變量,避免頻繁創(chuàng)建它的對象實例,如下代碼:   

  1. public class SimpleDateFormatTest {  
  2.         private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  3.         public static String formatDate(Date date) throws ParseException {  
  4.             return sdf.format(date);  
  5.         }  
  6.         public static Date parse(String strDate) throws ParseException {  
  7.             return sdf.parse(strDate);  
  8.         }  
  9.         public static void main(String[] args) throws InterruptedException, ParseException {  
  10.             System.out.println(sdf.format(new Date()));  
  11.         }  
  12.     } 

是不是感覺沒什么毛病?單線程下自然沒毛病了,都是運用到多線程下就有大問題了。測試下:       

  1. public static void main(String[] args) throws InterruptedException, ParseException {  
  2.             ExecutorService service = Executors.newFixedThreadPool(100);  
  3.             for (int i = 0; i < 20; i++) {  
  4.                 service.execute(() -> {  
  5.                     for (int j = 0; j < 10; j++) {  
  6.                         try {  
  7.                             System.out.println(parse("2018-01-02 09:45:59"));  
  8.                         } catch (ParseException e) {  
  9.                             e.printStackTrace();  
  10.                         }  
  11.                     }  
  12.                 });  
  13.             }  
  14.             // 等待上述的線程執(zhí)行完  
  15.             service.shutdown(); 
  16.             service.awaitTermination(1, TimeUnit.DAYS);  
  17.         } 

控制臺打印結(jié)果:

你看這不崩了?部分線程獲取的時間不對,部分線程直接報 java.lang.NumberFormatException:multiple points錯,線程直接掛死了。

三.多線程不安全原因

因為我們把SimpleDateFormat定義為靜態(tài)變量,那么多線程下SimpleDateFormat的實例就會被多個線程共享,B線程會讀取到A線程的時間,就會出現(xiàn)時間差異和其它各種問題。SimpleDateFormat和它繼承的DateFormat類也不是線程安全的

來看看SimpleDateFormat的format()方法的源碼     

  1. // Called from Format after creating a FieldDelegate  
  2.       private StringBuffer format(Date date, StringBuffer toAppendTo, 
  3.                                   FieldDelegate delegate) {  
  4.           // Convert input date to time field list  
  5.           calendar.setTime(date);  
  6.           boolean useDateFormatSymbolsuseDateFormatSymbols = useDateFormatSymbols();  
  7.           for (int i = 0; i < compiledPattern.length; ) {  
  8.               int tag = compiledPattern[i] >>> 8;  
  9.               int count = compiledPattern[i++] & 0xff;  
  10.               if (count == 255) {  
  11.                   count = compiledPattern[i++] << 16 
  12.                   count |= compiledPattern[i++];  
  13.               }  
  14.               switch (tag) {  
  15.               case TAG_QUOTE_ASCII_CHAR:  
  16.                   toAppendTo.append((char)count);  
  17.                   break;  
  18.               case TAG_QUOTE_CHARS:  
  19.                   toAppendTo.append(compiledPattern, i, count);  
  20.                   i += count;  
  21.                   break;  
  22.               default:  
  23.                   subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);  
  24.                   break;  
  25.               }  
  26.           }  
  27.           return toAppendTo;  
  28.       } 

注意, calendar.setTime(date),SimpleDateFormat的format方法實際操作的就是Calendar。

因為我們聲明SimpleDateFormat為static變量,那么它的Calendar變量也就是一個共享變量,可以被多個線程訪問。

假設(shè)線程A執(zhí)行完calendar.setTime(date),把時間設(shè)置成2019-01-02,這時候被掛起,線程B獲得CPU執(zhí)行權(quán)。線程B也執(zhí)行到了calendar.setTime(date),把時間設(shè)置為2019-01-03。線程掛起,線程A繼續(xù)走,calendar還會被繼續(xù)使用(subFormat方法),而這時calendar用的是線程B設(shè)置的值了,而這就是引發(fā)問題的根源,出現(xiàn)時間不對,線程掛死等等。

其實SimpleDateFormat源碼上作者也給過我們提示:  

  1. * Date formats are not synchronized.  
  2.    * It is recommended to create separate format instances for each thread.  
  3.    * If multiple threads access a format concurrently, it must be synchronized  
  4.    * externally. 

意思就是

日期格式不同步。

建議為每個線程創(chuàng)建單獨的格式實例。 

如果多個線程同時訪問一種格式,則必須在外部同步該格式。

四.解決方案

只在需要的時候創(chuàng)建新實例,不用static修飾     

  1. public static String formatDate(Date date) throws ParseException {  
  2.           SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  3.           return sdf.format(date);  
  4.       } 
  5.       public static Date parse(String strDate) throws ParseException {  
  6.           SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  7.           return sdf.parse(strDate);  
  8.       } 

如上代碼,僅在需要用到的地方創(chuàng)建一個新的實例,就沒有線程安全問題,不過也加重了創(chuàng)建對象的負(fù)擔(dān),會頻繁地創(chuàng)建和銷毀對象,效率較低。

synchronized大法好       

  1. private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  2.         public static String formatDate(Date date) throws ParseException {  
  3.             synchronized(sdf){  
  4.                 return sdf.format(date);  
  5.             }  
  6.         }  
  7.         public static Date parse(String strDate) throws ParseException {  
  8.             synchronized(sdf){  
  9.                 return sdf.parse(strDate);  
  10.             }  
  11.         } 

簡單粗暴,synchronized往上一套也可以解決線程安全問題,缺點自然就是并發(fā)量大的時候會對性能有影響,線程阻塞。

ThreadLocal       

  1. private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {  
  2.            @Override  
  3.            protected DateFormat initialValue() {  
  4.                return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  5.            }  
  6.        };  
  7.        public static Date parse(String dateStr) throws ParseException {  
  8.            return threadLocal.get().parse(dateStr);  
  9.        }  
  10.        public static String format(Date date) {  
  11.            return threadLocal.get().format(date);  
  12.        } 

ThreadLocal可以確保每個線程都可以得到單獨的一個SimpleDateFormat的對象,那么自然也就不存在競爭問題了。

基于JDK1.8的DateTimeFormatter

也是《阿里巴巴開發(fā)手冊》給我們的解決方案,對之前的代碼進(jìn)行改造:   

  1. public class SimpleDateFormatTest {  
  2.         private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");  
  3.         public static String formatDate2(LocalDateTime date) {  
  4.             return formatter.format(date);  
  5.         }  
  6.         public static LocalDateTime parse2(String dateNow) {  
  7.             return LocalDateTime.parse(dateNow, formatter);  
  8.         }  
  9.         public static void main(String[] args) throws InterruptedException, ParseException {  
  10.             ExecutorService service = Executors.newFixedThreadPool(100);  
  11.             // 20個線程  
  12.             for (int i = 0; i < 20; i++) {  
  13.                 service.execute(() -> {  
  14.                     for (int j = 0; j < 10; j++) {  
  15.                         try {  
  16.                             System.out.println(parse2(formatDate2(LocalDateTime.now())));  
  17.                         } catch (Exception e) {  
  18.                             e.printStackTrace();  
  19.                         }  
  20.                     }  
  21.                 });  
  22.             }  
  23.             // 等待上述的線程執(zhí)行完  
  24.             service.shutdown();  
  25.             service.awaitTermination(1, TimeUnit.DAYS);  
  26.         }  
  27.     } 

運行結(jié)果就不貼了,不會出現(xiàn)報錯和時間不準(zhǔn)確的問題。

DateTimeFormatter源碼上作者也加注釋說明了,他的類是不可變的,并且是線程安全的。 

  1. * This class is immutable and thread-safe. 

 

 

責(zé)任編輯:龐桂玉 來源: Java編程
相關(guān)推薦

2021-11-19 11:50:48

MyBatisforeachJava

2019-03-22 09:13:47

淘寶12306閑魚

2023-12-04 09:14:00

數(shù)據(jù)庫MySQL

2023-01-07 17:41:36

線程池并發(fā)

2024-08-26 15:17:16

2014-01-09 14:52:47

創(chuàng)意開源

2022-10-28 09:51:18

PrintfLinux開發(fā)

2025-06-16 01:30:00

大數(shù)據(jù)

2019-10-10 10:12:25

AI職業(yè)人工智能

2024-09-02 09:31:19

2012-07-19 10:03:32

2012-05-25 09:35:48

Ubuntu操作系統(tǒng)

2012-05-15 08:38:19

升級NASSATA

2015-07-20 13:39:17

Windows 10數(shù)據(jù)

2010-11-23 10:53:40

騎驢找馬

2024-11-12 16:28:34

2016-10-12 13:37:09

LombokIDEidea

2019-07-01 15:19:14

機(jī)器學(xué)習(xí)ML代碼

2023-11-03 08:28:19

2020-01-20 14:30:39

HTTP概念標(biāo)頭
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 国产成人免费视频网站高清观看视频 | 色女人天堂 | 欧美性大战xxxxx久久久 | 一区二区三区四区视频 | 欧美成人一区二区三区 | 亚洲精品乱码久久久久久按摩观 | 在线观看免费高清av | 日本a∨精品中文字幕在线 亚洲91视频 | 久草a√| 欧美日日 | 日韩一区二区在线观看视频 | 国产丝袜一区二区三区免费视频 | 一级毛片色一级 | 五月婷婷在线视频 | 一区在线免费视频 | 91视频在线观看免费 | 中文二区 | 亚洲视频在线一区 | 国产三区在线观看视频 | 91性高湖久久久久久久久_久久99 | 精品一区二区三区四区 | 久久伦理中文字幕 | 国产一区不卡 | av中文字幕网 | 91久久 | 91.色| 国产精品欧美一区二区 | 国产精品特级片 | 午夜久久久 | 黄色成人在线观看 | 四虎影院免费在线播放 | 国产亚洲精品区 | 国产在线一区二区三区 | 国产高清一区二区三区 | 成人小视频在线观看 | 精品中文字幕在线观看 | 亚洲一区二区三区在线视频 | 91精品无人区卡一卡二卡三 | 国产乱性 | 日韩av成人在线观看 | 91精品国产91久久久久游泳池 |