自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

還在使用SimpleDateFormat?你的項(xiàng)目崩沒?

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

[[315752]]

一.前言

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

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

公眾號(hào)后臺(tái)回復(fù)"阿里巴巴開發(fā)手冊(cè)"獲取《阿里巴巴開發(fā)手冊(cè)》v 1.4.0

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

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

  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.     } 

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

  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.         } 

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

你看這不崩了?部分線程獲取的時(shí)間不對(duì),部分線程直接報(bào) java.lang.NumberFormatException:multiple points錯(cuò),線程直接掛死了。

三.多線程不安全原因

因?yàn)槲覀儼裇impleDateFormat定義為靜態(tài)變量,那么多線程下SimpleDateFormat的實(shí)例就會(huì)被多個(gè)線程共享,B線程會(huì)讀取到A線程的時(shí)間,就會(huì)出現(xiàn)時(shí)間差異和其它各種問題。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方法實(shí)際操作的就是Calendar。

因?yàn)槲覀兟暶鱏impleDateFormat為static變量,那么它的Calendar變量也就是一個(gè)共享變量,可以被多個(gè)線程訪問。

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

其實(shí)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. 

意思就是

日期格式不同步。

建議為每個(gè)線程創(chuàng)建單獨(dú)的格式實(shí)例。 

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

四.解決方案

只在需要的時(shí)候創(chuàng)建新實(shí)例,不用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)建一個(gè)新的實(shí)例,就沒有線程安全問題,不過也加重了創(chuàng)建對(duì)象的負(fù)擔(dān),會(huì)頻繁地創(chuàng)建和銷毀對(duì)象,效率較低。

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.         } 

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

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可以確保每個(gè)線程都可以得到單獨(dú)的一個(gè)SimpleDateFormat的對(duì)象,那么自然也就不存在競(jìng)爭(zhēng)問題了。

基于JDK1.8的DateTimeFormatter

也是《阿里巴巴開發(fā)手冊(cè)》給我們的解決方案,對(duì)之前的代碼進(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個(gè)線程  
  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.     } 

運(yùn)行結(jié)果就不貼了,不會(huì)出現(xiàn)報(bào)錯(cuò)和時(shí)間不準(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ù)庫(kù)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ā)

2019-10-10 10:12:25

AI職業(yè)人工智能

2024-09-02 09:31:19

2012-05-15 08:38:19

升級(jí)NASSATA

2012-05-25 09:35:48

Ubuntu操作系統(tǒng)

2012-07-19 10:03:32

2015-07-20 13:39:17

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

2016-10-12 13:37:09

LombokIDEidea

2010-11-23 10:53:40

騎驢找馬

2025-04-08 03:00:00

SpringDocker容器

2024-11-12 16:28:34

2023-11-03 08:28:19

2019-07-01 15:19:14

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

2017-09-18 10:48:50

點(diǎn)贊
收藏

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