日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

为什么SimpleDateFormat不是线程安全的?

發(fā)布時(shí)間:2023/12/10 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 为什么SimpleDateFormat不是线程安全的? 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、前言

日期的轉(zhuǎn)換與格式化在項(xiàng)目中應(yīng)該是比較常用的了,最近同事小剛出去面試實(shí)在是沒想到被 SimpleDateFormat 給擺了一道…

面試官:項(xiàng)目中的日期轉(zhuǎn)換怎么用的?SimpleDateFormat 用過嗎?能說一下 SimpleDateFormat 線程安全問題嗎,以及如何解決?

同事小剛:用過的,平時(shí)就是在全局定義一個(gè) static 的 SimpleDateFormat,然后在業(yè)務(wù)處理方法(controller)中直接使用,至于線程安全… 這個(gè)… 倒是沒遇到過線程安全問題。

哎,面試官的考察點(diǎn)真的是難以捉摸,吐槽歸吐槽,一起來看看這個(gè)類吧。

二、概述

SimpleDateFormat 類主要負(fù)責(zé)日期的轉(zhuǎn)換與格式化等操作,在多線程的環(huán)境中,使用此類容易造成數(shù)據(jù)轉(zhuǎn)換及處理的不正確,因?yàn)?SimpleDateFormat 類并不是線程安全的,但在單線程環(huán)境下是沒有問題的。

SimpleDateFormat 在類注釋中也提醒大家不適用于多線程場景:

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.日期格式不同步。 建議為每個(gè)線程創(chuàng)建單獨(dú)的格式實(shí)例。 如果多個(gè)線程同時(shí)訪問一種格式,則必須在外部同步該格式。

來看看阿里巴巴 java 開發(fā)規(guī)范是怎么描述 SimpleDateFormat 的:

三、模擬線程安全問題

無碼無真相,接下來我們創(chuàng)建一個(gè)線程來模擬 SimpleDateFormat 線程安全問題:

創(chuàng)建 MyThread.java 類:

public class MyThread extends Thread{private SimpleDateFormat simpleDateFormat;// 要轉(zhuǎn)換的日期字符串private String dateString;public MyThread(SimpleDateFormat simpleDateFormat, String dateString){this.simpleDateFormat = simpleDateFormat;this.dateString = dateString;}@Overridepublic void run() {try {Date date = simpleDateFormat.parse(dateString);String newDate = simpleDateFormat.format(date).toString();if(!newDate.equals(dateString)){System.out.println("ThreadName=" + this.getName()+ " 報(bào)錯(cuò)了,日期字符串:" + dateString+ " 轉(zhuǎn)換成的日期為:" + newDate);}}catch (ParseException e){e.printStackTrace();}} }

創(chuàng)建執(zhí)行類 Test.java 類:

public class Test {// 一般我們使用SimpleDateFormat的時(shí)候會把它定義為一個(gè)靜態(tài)變量,避免頻繁創(chuàng)建它的對象實(shí)例private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("YYYY-MM-dd");public static void main(String[] args) {String[] dateStringArray = new String[] { "2020-09-10", "2020-09-11", "2020-09-12", "2020-09-13", "2020-09-14"};MyThread[] myThreads = new MyThread[5];// 創(chuàng)建線程for (int i = 0; i < 5; i++) {myThreads[i] = new MyThread(simpleDateFormat, dateStringArray[i]);}// 啟動線程for (int i = 0; i < 5; i++) {myThreads[i].start();}} }

執(zhí)行截圖如下:

從控制臺打印的結(jié)果來看,使用單例的 SimpleDateFormat 類在多線程的環(huán)境中處理日期轉(zhuǎn)換,極易出現(xiàn)轉(zhuǎn)換異常(java.lang.NumberFormatException:multiple points)以及轉(zhuǎn)換錯(cuò)誤的情況。

四、線程不安全的原因

這個(gè)時(shí)候就需要看看源碼了,format() 格式轉(zhuǎn)換方法:

// 成員變量 Calendar protected Calendar calendar;private StringBuffer format(Date date, StringBuffer toAppendTo,FieldDelegate delegate) {// Convert input date to time field listcalendar.setTime(date);boolean useDateFormatSymbols = useDateFormatSymbols();for (int i = 0; i < compiledPattern.length; ) {int tag = compiledPattern[i] >>> 8;int count = compiledPattern[i++] & 0xff;if (count == 255) {count = compiledPattern[i++] << 16;count |= compiledPattern[i++];}switch (tag) {case TAG_QUOTE_ASCII_CHAR:toAppendTo.append((char)count);break;case TAG_QUOTE_CHARS:toAppendTo.append(compiledPattern, i, count);i += count;break;default:subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);break;}}return toAppendTo; }

我們把重點(diǎn)放在 calendar ,這個(gè) format 方法在執(zhí)行過程中,會操作成員變量 calendar 來保存時(shí)間 calendar.setTime(date) 。

但由于在聲明 SimpleDateFormat 的時(shí)候,使用的是 static 定義的,那么這個(gè) SimpleDateFormat 就是一個(gè)共享變量,SimpleDateFormat 中的 calendar 也就可以被多個(gè)線程訪問到,所以問題就出現(xiàn)了,舉個(gè)例子:

假設(shè)線程 A 剛執(zhí)行完 calendar.setTime(date) 語句,把時(shí)間設(shè)置為 2020-09-01,但線程還沒執(zhí)行完,線程 B 又執(zhí)行了 calendar.setTime(date) 語句,把時(shí)間設(shè)置為 2020-09-02,這個(gè)時(shí)候就出現(xiàn)幻讀了,線程 A 繼續(xù)執(zhí)行下去的時(shí)候,拿到的 calendar.getTime 得到的時(shí)間就是線程B改過之后的。

除了 format() 方法以外,SimpleDateFormat 的 parse 方法也有同樣的問題。

至此,我們發(fā)現(xiàn)了 SimpleDateFormat 的弊端,所以為了解決這個(gè)問題就是不要把 SimpleDateFormat 當(dāng)做一個(gè)共享變量來使用。

五、如何解決線程安全

1、每次使用就創(chuàng)建一個(gè)新的 SimpleDateFormat

創(chuàng)建全局工具類 DateUtils.java

public class DateUtils {public static Date parse(String formatPattern, String dateString) throws ParseException {return new SimpleDateFormat(formatPattern).parse(dateString);}public static String format(String formatPattern, Date date){return new SimpleDateFormat(formatPattern).format(date);} }

所有用到 SimpleDateFormat 的地方全部用 DateUtils 替換,然后看一下執(zhí)行結(jié)果:

好家伙,異常+錯(cuò)誤終于是沒了,這種解決處理錯(cuò)誤的原理就是創(chuàng)建了多個(gè) SimpleDateFormat 類的實(shí)例,在需要用到的地方創(chuàng)建一個(gè)新的實(shí)例,就沒有線程安全問題,不過也加重了創(chuàng)建對象的負(fù)擔(dān),會頻繁地創(chuàng)建和銷毀對象,效率較低。

2、synchronized 鎖

synchronized 就不展開介紹了,不了解的小伙伴請移步 > synchronized的底層原理?

變更一下 DateUtils.java

public class DateUtils {private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static Date parse(String formatPattern, String dateString) throws ParseException {synchronized (simpleDateFormat){return simpleDateFormat.parse(dateString);}}public static String format(String formatPattern, Date date) {synchronized (simpleDateFormat){return simpleDateFormat.format(date);}} }

簡單粗暴,synchronized 往上一套也可以解決線程安全問題,缺點(diǎn)自然就是并發(fā)量大的時(shí)候會對性能有影響,因?yàn)槭褂昧?synchronized 加鎖后的多線程就相當(dāng)于串行,線程阻塞,執(zhí)行效率低。

3、ThreadLocal(最佳MVP)

ThreadLocal 是 java 里一種特殊的變量,ThreadLocal 提供了線程本地的實(shí)例,它與普通變量的區(qū)別在于,每個(gè)使用該線程變量的線程都會初始化一個(gè)完全獨(dú)立的實(shí)例副本。

繼續(xù)改造 DateUtils.java

public class DateUtils {private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){@Overrideprotected DateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd");}};public static Date parse(String formatPattern, String dateString) throws ParseException {return threadLocal.get().parse(dateString);}public static String format(String formatPattern, Date date) {return threadLocal.get().format(date);} }

ThreadLocal 可以確保每個(gè)線程都可以得到單獨(dú)的一個(gè) SimpleDateFormat 的對象,那么就不會存在競爭問題。

如果項(xiàng)目中還在使用 SimpleDateFormat 的話,推薦這種寫法,但這樣就結(jié)束了嗎?

顯然不是…

六、項(xiàng)目中推薦的寫法

上邊提到的阿里巴巴 java 開發(fā)手冊給出了說明:如果是 JDK8 的應(yīng)用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe。

日期轉(zhuǎn)換,SimpleDateFormat 固然好用,但是現(xiàn)在我們已經(jīng)有了更好地選擇,Java 8 引入了新的日期時(shí)間 API,并引入了線程安全的日期類,一起來看看。

  • Instant:瞬時(shí)實(shí)例。
  • LocalDate:本地日期,不包含具體時(shí)間 例如:2014-01-14 可以用來記錄生日、紀(jì)念日、加盟日等。
  • LocalTime:本地時(shí)間,不包含日期。
  • LocalDateTime:組合了日期和時(shí)間,但不包含時(shí)差和時(shí)區(qū)信息。
  • ZonedDateTime:最完整的日期時(shí)間,包含時(shí)區(qū)和相對UTC或格林威治的時(shí)差。

新API還引入了 ZoneOffSet 和 ZoneId 類,使得解決時(shí)區(qū)問題更為簡便。

解析、格式化時(shí)間的 DateTimeFormatter 類也進(jìn)行了全部重新設(shè)計(jì)。

例如,我們使用 LocalDate 代替 Date,使用 DateTimeFormatter 代替 SimpleDateFormat,如下所示:

// 當(dāng)前日期和時(shí)間 String DateNow = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")); System.out.println(DateNow);

這樣就避免了 SimpleDateFormat 的線程不安全問題啦。

此時(shí)的 DateUtils.java

public class DateUtils {public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");public static LocalDate parse(String dateString){return LocalDate.parse(dateString, DATE_TIME_FORMATTER);}public static String format(LocalDate target) {return target.format(DATE_TIME_FORMATTER);} }

七、最后總結(jié)

SimpleDateFormart 線程不安全問題

SimpleDateFormart 繼承自 DateFormart,在 DataFormat 類內(nèi)部有一個(gè) Calendar 對象引用,SimpleDateFormat 轉(zhuǎn)換日期都是靠這個(gè) Calendar 對象來操作的,比如 parse(String),format(date) 等類似的方法,Calendar 在用的時(shí)候是直接使用的,而且是改變了 Calendar 的值,這樣情況在多線程下就會出現(xiàn)線程安全問題,如果 SimpleDateFormart 是靜態(tài)的話,那么多個(gè) thread 之間就會共享這個(gè) SimpleDateFormart,同時(shí)也會共享這個(gè) Calendar 引用,那么就出現(xiàn)數(shù)據(jù)賦值覆蓋情況,也就是線程安全問題。(現(xiàn)在項(xiàng)目中用到日期轉(zhuǎn)換,都是使用的 java 8 中的 LocalDate,或者 LocalDateTime,本質(zhì)是這些類是不可變類,不可變一定程度上保證了線程安全)。

解決方式

在多線程下可以使用 ThreadLocal 修飾 SimpleDateFormart,ThreadLocal 可以確保每個(gè)線程都可以得到單獨(dú)的一個(gè) SimpleDateFormat 的對象,那么就不會存在競爭問題。

項(xiàng)目中推薦的寫法

java 8 中引入新的日期類 API,這些類是不可變的,且線程安全的。

以后面試官再問項(xiàng)目中怎么使用日期轉(zhuǎn)換的,盡量就不要說 SimpleDateFormat 了。

博客園持續(xù)更新,歡迎關(guān)注,未來,我們一起成長。

本文首發(fā)于博客園:https://www.cnblogs.com/niceyoo/p/13672913.html

總結(jié)

以上是生活随笔為你收集整理的为什么SimpleDateFormat不是线程安全的?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。