最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SimpleDateFormat線程安全問題排查詳解

 更新時(shí)間:2022年11月09日 08:56:23   作者:夕陽醉了  
這篇文章主要為大家介紹了SimpleDateFormat線程安全問題排查詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

一. 問題現(xiàn)象

運(yùn)營(yíng)部門反饋使用小程序配置的拉新現(xiàn)金紅包活動(dòng)二維碼,在掃碼后跳轉(zhuǎn)至404頁面。

二. 原因排查

首先,檢查掃碼后的跳轉(zhuǎn)鏈接地址不是對(duì)應(yīng)二維碼的實(shí)際URL,根據(jù)代碼邏輯推測(cè),可能是accessToken在微信端已失效導(dǎo)致,檢查數(shù)據(jù)發(fā)現(xiàn),數(shù)據(jù)庫存儲(chǔ)的accessToken過期時(shí)間為2022-11-29(排查問題當(dāng)日為2022-10-08),發(fā)現(xiàn)過期時(shí)間太長(zhǎng),導(dǎo)致accessToken未刷新導(dǎo)致。

接下來,繼續(xù)排查造成這一問題的真正原因。排查日志發(fā)現(xiàn)更新sql語句對(duì)應(yīng)的的過期時(shí)間與數(shù)據(jù)庫記錄的一致,推測(cè)賦值代碼存在問題,如下。

tokenInfo.setExpireTime(simpleDateFormat.parse(token.getString("expireTime")));

其中,simpleDateFormat在代碼中定義是該類的成員變量。

  • 跟蹤代碼后發(fā)現(xiàn)源碼中有明確說明SimpleDateFormat不應(yīng)該應(yīng)用于多線程場(chǎng)景下。
Synchronization
//SimpleDateFormat中的日期格式化不是同步的。
Date formats are not synchronized. 
//建議為每個(gè)線程創(chuàng)建獨(dú)立的格式實(shí)例。
It is recommended to create separate format instances for each thread. 
//如果多個(gè)線程同時(shí)訪問一個(gè)格式,則它必須保持外部同步。
If multiple threads access a format concurrently, it must be synchronized externally. 
  • 至此,基本可以判斷是simpleDateFormat.parse在多線程情況下造成錯(cuò)誤的過期時(shí)間入庫,導(dǎo)致accesstoken無法正常更新。

三. 原因分析

  • 接下來寫個(gè)測(cè)試類來模擬:
@RunWith(SpringRunner.class)
@SpringBootTest
public class SimpleDateFormatTest {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * 定義線程池
     **/
    private static final ExecutorService threadPool = new ThreadPoolExecutor(16,
            20,
            0L,
            TimeUnit.MILLISECONDS,
            new LinkedBlockingDeque<>(1024),
            new ThreadFactoryBuilder().setNamePrefix("[線程]").build(),
            new ThreadPoolExecutor.AbortPolicy()
    );
    @SneakyThrows
    @Test
    public void testParse() {
        Set<String> results = Collections.synchronizedSet(new HashSet<>());
        // 每個(gè)線程都對(duì)相同字符串執(zhí)行“parse日期字符串”的操作,當(dāng)THREAD_NUMBERS個(gè)線程執(zhí)行完畢后,應(yīng)該有且僅有一個(gè)相同的結(jié)果才是正確的
        String initialDateStr = "2022-10-08 18:30:01";
        for (int i = 0; i < 20; i++) {
            threadPool.execute(() -> {
                Date parse = null;
                try {
                    parse = simpleDateFormat.parse(initialDateStr);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "---" + parse);
            });
        }
        threadPool.shutdown();
        threadPool.awaitTermination(1, TimeUnit.HOURS);
    }
}

運(yùn)行結(jié)果如下:

[線程]5---Sat Jan 08 18:30:01 CST 2000
[線程]0---Wed Oct 08 18:30:01 CST 2200
[線程]4---Sat Oct 08 18:30:01 CST 2022
Exception in thread "[線程]3" java.lang.NumberFormatException: multiple points
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    at java.text.DigitList.getDouble(DigitList.java:169)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at com.SimpleDateFormatTest.lambda$testParse$0(SimpleDateFormatTest.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
[線程]6---Sat Oct 08 18:30:01 CST 2022
[線程]11---Wed Mar 15 18:30:01 CST 2045
Exception in thread "[線程]2" java.lang.ArrayIndexOutOfBoundsException: 275
    at sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:453)
    at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2397)
    at java.util.GregorianCalendar.computeTime(GregorianCalendar.java:2818)
    at java.util.Calendar.updateTime(Calendar.java:3393)
    at java.util.Calendar.getTimeInMillis(Calendar.java:1782)
    at java.util.Calendar.getTime(Calendar.java:1755)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1532)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at com.SimpleDateFormatTest.lambda$testParse$0(SimpleDateFormatTest.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
[線程]6---Fri Oct 01 18:30:01 CST 8202
[線程]12---Sat Oct 08 18:30:01 CST 2022
Exception in thread "[線程]1" java.lang.NumberFormatException: multiple points
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    at java.text.DigitList.getDouble(DigitList.java:169)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at com.SimpleDateFormatTest.lambda$testParse$0(SimpleDateFormatTest.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
[線程]0---Sat Oct 08 18:30:01 CST 2022
[線程]12---Sat Oct 08 18:30:01 CST 2022
[線程]13---Sat Oct 08 18:30:01 CST 2022
[線程]18---Sat Oct 08 18:30:01 CST 2022
[線程]6---Sat Oct 01 18:30:01 CST 2022
[線程]7---Sat Oct 08 18:30:01 CST 2022
[線程]10---Sat Oct 08 18:30:01 CST 2022
[線程]15---Sat Oct 08 18:00:01 CST 2022
[線程]17---Sat Oct 08 18:30:01 CST 2022
[線程]14---Sat Oct 08 18:30:01 CST 2022
預(yù)期結(jié)果個(gè)數(shù) 1---實(shí)際結(jié)果個(gè)數(shù)7

不僅有的線程結(jié)果不正確,甚至還有一些線程還出現(xiàn)了異常!

  • 為什么SimpleDateFormat類不是線程安全的?

SimpleDateFormat繼承了DateFormat,DateFormat內(nèi)部有一個(gè)Calendar對(duì)象的引用,主要用來存儲(chǔ)和SimpleDateFormat相關(guān)的日期信息。

SimpleDateFormat對(duì)parse()方法的實(shí)現(xiàn)。關(guān)鍵代碼如下:

 @Override
public Date parse(String text, ParsePosition pos) {
    ...省略中間代碼
    Date parsedDate;
    try {
        ...
        parsedDate = calb.establish(calendar).getTime();
    } catch (IllegalArgumentException e) {
       ...
    }
    return parsedDate;
}

establish()的實(shí)現(xiàn)如下:

Calendar establish(Calendar cal) {
    ...省略中間代碼
    cal.clear();
    for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
        for (int index = 0; index <= maxFieldIndex; index++) {
            if (field[index] == stamp) {
                cal.set(index, field[MAX_FIELD + index]);
                break;
            }
        }
    }
    ...
    return cal;
}

在多個(gè)線程共享SimpleDateFormat時(shí),同時(shí)也共享了Calendar引用,在如上代碼中,calendar首先會(huì)進(jìn)行clear()操作,然后進(jìn)行set操作,在多線程情況下,set操作會(huì)覆蓋之前的值,而且在后續(xù)對(duì)日期進(jìn)行操作時(shí),也可能會(huì)因?yàn)閏lear操作被清除導(dǎo)致異常。

四. 解決方案

  • 將SimpleDateFormat定義成局部變量,每次使用時(shí)都new一個(gè)新對(duì)象,頻繁創(chuàng)建對(duì)象消耗大,性能影響一些(JDK文檔推薦此做法)
    public static Date parse(String strDate) throws ParseException {
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         return sdf.parse(strDate);
    }
  • 維護(hù)一個(gè)SimpleDateFormat實(shí)體,轉(zhuǎn)換方法上使用 Synchronized 保證線程安全:多線程堵塞(并發(fā)大系統(tǒng)不推薦)
    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static String formatDate(Date date)throws ParseException{
        synchronized(sdf){
            return sdf.format(date);
        }  
    }
    public static Date parse(String strDate) throws ParseException{
        synchronized(sdf){
            return sdf.parse(strDate);
        }
    } 
  • 使用ThreadLocal : 線程獨(dú)享不堵塞,并且減少創(chuàng)建對(duì)象的開銷(如果對(duì)性能要求比較高的情況,推薦這種方式)。
    public static ThreadLocal<DateFormat> threadLocal = ThreadLocal.withInitial(
            () -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    );
    public static Date parse(String strDate) throws ParseException {
        return threadLocal.get().parse(strDate);
    }
  • DateTimeFormatter是Java8提供的新的日期時(shí)間API中的類,DateTimeFormatter類是線程安全的,可以在高并發(fā)場(chǎng)景下直接使用。
    String dateTimeStr= "2016-10-25 12:00:00";
    DateTimeFormatter formatter02 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    LocalDateTime localDateTime = LocalDateTime.parse(dateTimeStr,formatter02);
    System.out.println(localDateTime);
    String format = localDateTime.format(formatter02);
    System.out.println(format);
    2016-10-25T12:00
    2016-10-25 12:00:00

最終,我們根據(jù)實(shí)際情況公共包DateUtil類提供的strConvertDate方法,原理是按照方案1來解決該問題。

以上就是SimpleDateFormat線程安全問題排查詳解的詳細(xì)內(nèi)容,更多關(guān)于SimpleDateFormat線程安全排查的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java的原子類無鎖并發(fā)利器詳解

    Java的原子類無鎖并發(fā)利器詳解

    這篇文章主要介紹了Java的原子類無鎖并發(fā)利器詳解,原子類同樣能夠解決互斥性問題、原子性問題除此之外,因?yàn)樵宇愂菬o鎖操作,沒有用互斥鎖解決帶來的加鎖解決性能消耗,這種絕佳方案是怎么做到的呢,需要的朋友可以參考下
    2023-12-12
  • MybatisPlus為何可以不用@MapperScan詳解

    MybatisPlus為何可以不用@MapperScan詳解

    這篇文章主要給大家介紹了關(guān)于MybatisPlus為何可以不用@MapperScan的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用MybatisPlus具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2023-04-04
  • 基于swing開發(fā)彈幕播放器

    基于swing開發(fā)彈幕播放器

    這篇文章主要為大家詳細(xì)介紹了基于swing實(shí)現(xiàn)彈幕播放器的開發(fā)過程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • Java實(shí)現(xiàn)Dbhelper支持大數(shù)據(jù)增刪改

    Java實(shí)現(xiàn)Dbhelper支持大數(shù)據(jù)增刪改

    這篇文章主要介紹了Java實(shí)現(xiàn)Dbhelper支持大數(shù)據(jù)增刪改功能的實(shí)現(xiàn)過程,感興趣的小伙伴們可以參考一下
    2016-01-01
  • Java中的?CyclicBarrier詳解

    Java中的?CyclicBarrier詳解

    這篇文章主要介紹了Java中的?CyclicBarrier詳解,CyclicBarrier沒有顯示繼承哪個(gè)父類或者實(shí)現(xiàn)哪個(gè)父接口,?所有AQS和重入鎖不是通過繼承實(shí)現(xiàn)的,而是通過組合實(shí)現(xiàn)的,下文相關(guān)內(nèi)容需要的小伙伴可以參考一下
    2022-04-04
  • SpringBoot詳解自定義Stater的應(yīng)用

    SpringBoot詳解自定義Stater的應(yīng)用

    Springboot的出現(xiàn)極大的簡(jiǎn)化了開發(fā)人員的配置,而這之中的一大利器便是springboot的starter,starter是springboot的核心組成部分,springboot官方同時(shí)也為開發(fā)人員封裝了各種各樣方便好用的starter模塊
    2022-07-07
  • 淺析SpringBoot2底層注解@Conditional@ImportResource

    淺析SpringBoot2底層注解@Conditional@ImportResource

    這篇文章主要為大家介紹了SpringBoot2底層注解@Conditional@ImportResource的分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • 詳解servlet調(diào)用的幾種簡(jiǎn)單方式總結(jié)

    詳解servlet調(diào)用的幾種簡(jiǎn)單方式總結(jié)

    這篇文章主要介紹了詳解servlet調(diào)用的幾種簡(jiǎn)單方式總結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • idea2019版Plugins中搜索不到任何插件的問題解決

    idea2019版Plugins中搜索不到任何插件的問題解決

    本文主要介紹了idea2019版Plugins中搜索不到任何插件的問題解決,插件搜不出來的主要原因是plugins.jetbrains.com ping不通,下面就來介紹一下解決方法,感興趣的可以了解一下
    2023-09-09
  • SpringBoot如何使用slf4j日志及其項(xiàng)目配置、MVC支持

    SpringBoot如何使用slf4j日志及其項(xiàng)目配置、MVC支持

    這篇文章主要介紹了SpringBoot如何使用slf4j日志及其項(xiàng)目配置、MVC支持,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05

最新評(píng)論

调兵山市| 卫辉市| 株洲县| 达州市| 潼关县| 鹤壁市| 朝阳县| 桃江县| 张家港市| 长白| 信阳市| 甘德县| 武义县| 辽中县| 武宁县| 开平市| 巴青县| 台东市| 壶关县| 宁国市| 竹溪县| 始兴县| 渭源县| 元氏县| 吕梁市| 敦化市| 枝江市| 高邮市| 鹤岗市| 青龙| 清远市| 镇江市| 曲松县| 康定县| 攀枝花市| 嘉义县| 拉萨市| 凤冈县| 盐城市| 赤城县| 德格县|