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

淺談關(guān)于Java中TimeZone鎖競(jìng)爭(zhēng)引發(fā)的問(wèn)題解決

 更新時(shí)間:2026年02月06日 09:04:42   作者:樺說(shuō)編程  
本文主要介紹了淺談關(guān)于Java中TimeZone鎖競(jìng)爭(zhēng)引發(fā)的問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

背景

在高并發(fā)服務(wù)的性能排查中,我們通過(guò)線程 dump 發(fā)現(xiàn)了大量線程阻塞在同一把鎖上。本文將詳細(xì)分析問(wèn)題根因,并介紹我的優(yōu)化方案。

問(wèn)題發(fā)現(xiàn)

在生產(chǎn)環(huán)境進(jìn)行 thread dump 時(shí),發(fā)現(xiàn)多個(gè)工作線程(20+)處于 BLOCKED 狀態(tài),等待同一個(gè) Class 對(duì)象鎖:

at java.util.TimeZone.getTimeZone(TimeZone.java:549)
    - waiting on java.lang.Class@37b99a71

at org.joda.time.DateTimeZone.toTimeZone(DateTimeZone.java:1250)
at org.joda.time.base.AbstractDateTime.toGregorianCalendar(AbstractDateTime.java:296)
at xxx.common.DateTimeUtils.toCalendar(DateTimeUtils.java:469)
at xxx.business.RefundEndorseBusiness.doSetRefundEndorseFee(...)
...
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)

關(guān)鍵信息:

  • 阻塞位置: java.util.TimeZone.getTimeZone
  • 等待對(duì)象: java.lang.Class@37b99a71(類(lèi)鎖)
  • 調(diào)用來(lái)源: Joda-Time 的 DateTimeZone.toTimeZone() 方法

問(wèn)題分析

JDK TimeZone.getTimeZone 的鎖機(jī)制

TimeZone.getTimeZone(String ID) 方法內(nèi)部實(shí)現(xiàn)使用了 synchronized 關(guān)鍵字:

public static synchronized TimeZone getTimeZone(String ID) {
    // 從緩存或文件加載時(shí)區(qū)信息
    ...
}

這意味著所有調(diào)用該方法的線程都需要競(jìng)爭(zhēng)同一把類(lèi)鎖。

Joda-Time 的調(diào)用鏈

分析 Joda-Time 源碼,DateTime.toGregorianCalendar() 的實(shí)現(xiàn)如下:

// org.joda.time.base.AbstractDateTime
public GregorianCalendar toGregorianCalendar() {
    DateTimeZone zone = getZone();
    GregorianCalendar cal = new GregorianCalendar(zone.toTimeZone());
    cal.setTime(toDate());
    return cal;
}

每次調(diào)用都會(huì)執(zhí)行 zone.toTimeZone(),最終觸發(fā) TimeZone.getTimeZone()。

問(wèn)題根因

┌─────────────────────────────────────────────────────────────────┐
│                      高并發(fā)請(qǐng)求                                  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│   Thread-1    Thread-2    Thread-3    ...    Thread-N           │
│      │           │           │                   │              │
│      ▼           ▼           ▼                   ▼              │
│  toCalendar()  toCalendar()  toCalendar()   toCalendar()        │
│      │           │           │                   │              │
│      ▼           ▼           ▼                   ▼              │
│  toGregorianCalendar() ────────────────────────────────────     │
│      │           │           │                   │              │
│      ▼           ▼           ▼                   ▼              │
│  toTimeZone() ─────────────────────────────────────────────     │
│      │           │           │                   │              │
│      ▼           ▼           ▼                   ▼              │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │     TimeZone.getTimeZone() - synchronized 類(lèi)鎖           │   │
│  │                                                          │   │
│  │   Thread-1: 獲取鎖,執(zhí)行中...                             │   │
│  │   Thread-2: BLOCKED (waiting on java.lang.Class)        │   │
│  │   Thread-3: BLOCKED (waiting on java.lang.Class)        │   │
│  │   ...                                                    │   │
│  │   Thread-N: BLOCKED (waiting on java.lang.Class)        │   │
│  └──────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

核心問(wèn)題

  1. 每次時(shí)間轉(zhuǎn)換都觸發(fā) toTimeZone() 調(diào)用
  2. TimeZone.getTimeZone() 是同步方法(JDK 8)
  3. 高并發(fā)下大量線程競(jìng)爭(zhēng)同一把類(lèi)鎖
  4. 實(shí)際業(yè)務(wù)中時(shí)區(qū)種類(lèi)非常有限(如僅 Asia/Shanghai、UTC 等)

原始實(shí)現(xiàn)

public static Calendar toCalendar(DateTime dateTime) {
    Calendar result;
    if (dateTime == null || DateTimeUtils.isLogicMin(dateTime)) {
        result = new GregorianCalendar(1, 0, 1, 0, 0, 0);
        result.setTimeZone(TimeZone.getTimeZone("UTC"));
        return result;
    }
    if (DateTimeUtils.isLogicMax(dateTime)) {
        result = new GregorianCalendar(9999, Calendar.DECEMBER, 31, 0, 0, 0);
        result.setTimeZone(TimeZone.getTimeZone("UTC"));
        return result;
    }
    // 問(wèn)題所在:每次都調(diào)用 toGregorianCalendar()
    result = dateTime.toGregorianCalendar();
    return result;
}

優(yōu)化方案

設(shè)計(jì)思路

由于底層API在項(xiàng)目中頻繁使用,全部改動(dòng)風(fēng)險(xiǎn)比較大,我也不想因?yàn)槌鲥e(cuò)而背鍋,所以考慮消除鎖競(jìng)爭(zhēng)的方案。

既然時(shí)區(qū)數(shù)量有限,我們可以緩存 DateTimeZone 到 TimeZone 的映射,避免重復(fù)調(diào)用 toTimeZone()。

技術(shù)選型:為什么選擇 Caffeine

方案優(yōu)點(diǎn)缺點(diǎn)
HashMap + synchronized實(shí)現(xiàn)簡(jiǎn)單鎖粒度粗,與原問(wèn)題類(lèi)似
ConcurrentHashMap.computeIfAbsentJDK 原生,無(wú)依賴(lài)首次加載時(shí)仍有鎖競(jìng)爭(zhēng);長(zhǎng) key 可能 hash 沖突
Caffeine高性能、自動(dòng)驅(qū)逐、統(tǒng)計(jì)監(jiān)控引入額外依賴(lài)
Guava Cache功能完善性能略遜于 Caffeine

選擇 Caffeine 的原因:

  1. 高性能: 基于 W-TinyLFU 算法,近乎 O(1) 的讀寫(xiě)性能
  2. 無(wú)鎖讀取: 讀操作幾乎無(wú)鎖競(jìng)爭(zhēng)
  3. 自動(dòng)管理: 支持容量限制和自動(dòng)驅(qū)逐
  4. 項(xiàng)目已有依賴(lài): 無(wú)需引入新的 jar 包

優(yōu)化后的實(shí)現(xiàn)

/**
 * 緩存開(kāi)關(guān),驅(qū)逐時(shí)降級(jí)為直接調(diào)用
 */
static volatile boolean zoneCacheEnable = true;

/**
 * DateTimeZone -> TimeZone 緩存
 * 生產(chǎn)環(huán)境時(shí)區(qū)種類(lèi)有限(通常 < 5 種),設(shè)置 maximumSize = 24 足夠
 */
static LoadingCache<DateTimeZone, TimeZone> zoneCache = Caffeine.newBuilder()
        .maximumSize(24)
        .evictionListener((k, v, c) -> {
            // 正常情況不應(yīng)該觸發(fā)驅(qū)逐,如果觸發(fā)說(shuō)明時(shí)區(qū)種類(lèi)異常多
            LOGGER.error("zone cache evicted k={}, v={}, cause={}", k, v, c);
            zoneCacheEnable = false;
        })
        .build(DateTimeZone::toTimeZone);

/**
 * 從緩存獲取 TimeZone,帶 fallback
 */
static TimeZone getTimeZoneFromCache(DateTimeZone zone) {
    return zoneCacheEnable ? zoneCache.get(zone) : zone.toTimeZone();
}

/**
 * 優(yōu)化后的 toCalendar 方法
 */
public static Calendar toCalendar(DateTime dateTime) {
    Calendar result;
    if (dateTime == null || DateTimeUtils.isLogicMin(dateTime)) {
        result = new GregorianCalendar(1, 0, 1, 0, 0, 0);
        result.setTimeZone(TimeZone.getTimeZone("UTC"));
        return result;
    }
    if (DateTimeUtils.isLogicMax(dateTime)) {
        result = new GregorianCalendar(9999, Calendar.DECEMBER, 31, 0, 0, 0);
        result.setTimeZone(TimeZone.getTimeZone("UTC"));
        return result;
    }

    // 優(yōu)化:從緩存獲取 TimeZone,避免鎖競(jìng)爭(zhēng)
    DateTimeZone zone = dateTime.getZone();
    TimeZone timeZone = getTimeZoneFromCache(zone);
    GregorianCalendar cal = new GregorianCalendar(timeZone);
    cal.setTime(dateTime.toDate());
    return cal;
}

優(yōu)化前后對(duì)比

優(yōu)化前:
┌─────────────────────────────────────────────────────────┐
│  N 個(gè)線程同時(shí)調(diào)用 toCalendar()                           │
│           │                                             │
│           ▼                                             │
│  ┌─────────────────────────────────────────────────┐    │
│  │  TimeZone.getTimeZone() - synchronized 類(lèi)鎖     │    │
│  │  所有線程串行等待                                 │    │
│  └─────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────┘

優(yōu)化后:
┌─────────────────────────────────────────────────────────┐
│  N 個(gè)線程同時(shí)調(diào)用 toCalendar()                           │
│           │                                             │
│           ▼                                             │
│  ┌─────────────────────────────────────────────────┐    │
│  │  Caffeine Cache - 無(wú)鎖讀取                       │    │
│  │  所有線程并行獲取緩存的 TimeZone                   │    │
│  └─────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────┘

關(guān)鍵設(shè)計(jì)決策

1. 為什么設(shè)置 maximumSize = 24

  • 國(guó)內(nèi)業(yè)務(wù)主要涉及 Asia/Shanghai、UTCAsia/Hong_Kong 等少數(shù)幾個(gè)時(shí)區(qū)
  • 如果超過(guò) 24 個(gè)不同時(shí)區(qū),說(shuō)明數(shù)據(jù)異常,觸發(fā)告警

2. evictionListener 的作用

.evictionListener((k, v, c) -> {
    LOGGER.error("zone cache evicted k={}, v={}, cause={}", k, v, c);
    zoneCacheEnable = false;
})
  • 正常情況下不應(yīng)該觸發(fā)驅(qū)逐(時(shí)區(qū)數(shù)量 < 8)
  • 如果觸發(fā),說(shuō)明:
    • 存在異常數(shù)據(jù)導(dǎo)致時(shí)區(qū)種類(lèi)過(guò)多
    • 或者緩存配置需要調(diào)整
  • 記錄錯(cuò)誤日志便于排查
  • 禁用緩存作為 fallback,避免頻繁驅(qū)逐帶來(lái)的性能損耗

3. volatile 關(guān)鍵字的使用

static volatile boolean zoneCacheEnable = true;
  • 保證多線程間的可見(jiàn)性
  • 允許在發(fā)現(xiàn)異常時(shí)快速降級(jí)

總結(jié)

通過(guò)引入 Caffeine 緩存,我們成功消除了 TimeZone.getTimeZone() 的鎖競(jìng)爭(zhēng)問(wèn)題。這個(gè)優(yōu)化的核心思想是:對(duì)于數(shù)量有限、創(chuàng)建成本高的對(duì)象,使用緩存來(lái)?yè)Q取并發(fā)性能。

不要使用過(guò)時(shí)的API。

到此這篇關(guān)于淺談關(guān)于Java中TimeZone鎖競(jìng)爭(zhēng)引發(fā)的問(wèn)題解決的文章就介紹到這了,更多相關(guān)Java TimeZone鎖競(jìng)爭(zhēng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • EasyExcel核心實(shí)戰(zhàn)之Excel合并單元格,在線編輯與導(dǎo)出全攻略

    EasyExcel核心實(shí)戰(zhàn)之Excel合并單元格,在線編輯與導(dǎo)出全攻略

    在日常業(yè)務(wù)開(kāi)發(fā)中,Excel?報(bào)表三個(gè)字往往意味著復(fù)雜、凌亂和無(wú)限的加班,本文提供了一套完整的?EasyExcel?解決方案,涵蓋三大核心場(chǎng)景,?即合并單元格,在線編輯和數(shù)據(jù)導(dǎo)出,感興趣的小伙伴可以了解下
    2026-05-05
  • JavaWeb項(xiàng)目中JSP訪問(wèn)的問(wèn)題解決

    JavaWeb項(xiàng)目中JSP訪問(wèn)的問(wèn)題解決

    JSP文件一般有兩個(gè)存放位置,本文主要介紹了JavaWeb項(xiàng)目中JSP訪問(wèn)的問(wèn)題解決,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • spring實(shí)現(xiàn)bean對(duì)象創(chuàng)建代碼詳解

    spring實(shí)現(xiàn)bean對(duì)象創(chuàng)建代碼詳解

    這篇文章主要介紹了spring實(shí)現(xiàn)bean對(duì)象創(chuàng)建代碼詳解,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2017-12-12
  • 解讀CompletableFuture的底層原理

    解讀CompletableFuture的底層原理

    本文探討了Java8中CompletableFuture的原理和應(yīng)用,詳解其異步編程能力、工作機(jī)制及實(shí)際使用方法,CompletableFuture通過(guò)鏈?zhǔn)秸{(diào)用和狀態(tài)管理優(yōu)化異步任務(wù),提高Java應(yīng)用的效率和性能
    2024-09-09
  • 解決idea每次新建項(xiàng)目都需要重新指定maven目錄

    解決idea每次新建項(xiàng)目都需要重新指定maven目錄

    這篇文章主要介紹了解決idea每次新建項(xiàng)目都需要配置maven,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • Java利用Spire.PDF for Java實(shí)現(xiàn)添加、設(shè)置和刪除PDF圖層

    Java利用Spire.PDF for Java實(shí)現(xiàn)添加、設(shè)置和刪除PDF圖層

    PDF文檔作為信息交換的通用格式,其內(nèi)容的組織與管理至關(guān)重要,而PDF圖層正是實(shí)現(xiàn)這一目標(biāo)的神器,下面小編就為大家詳細(xì)講講如何使用Java高效操作PDF圖層吧
    2025-10-10
  • Mybatis邏輯分頁(yè)與物理分頁(yè)P(yáng)ageHelper使用解析

    Mybatis邏輯分頁(yè)與物理分頁(yè)P(yáng)ageHelper使用解析

    這篇文章主要為大家介紹了Mybatis邏輯分頁(yè)與物理分頁(yè)P(yáng)ageHelper使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Java中super和this關(guān)鍵字詳解

    Java中super和this關(guān)鍵字詳解

    這篇文章主要介紹了Java中super和this關(guān)鍵字詳解,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-06-06
  • Spring Cache 多租戶(hù)緩存隔離解決方案實(shí)踐

    Spring Cache 多租戶(hù)緩存隔離解決方案實(shí)踐

    本文提出通過(guò)自定義SpringCache的CacheResolver實(shí)現(xiàn)多租戶(hù)數(shù)據(jù)隔離,動(dòng)態(tài)添加租戶(hù)標(biāo)識(shí)至緩存鍵,解決緩存污染、數(shù)據(jù)泄露及失效范圍控制問(wèn)題,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-09-09
  • 解決IDEA 啟動(dòng)Tomcat控制臺(tái)亂碼問(wèn)題

    解決IDEA 啟動(dòng)Tomcat控制臺(tái)亂碼問(wèn)題

    今天在Idea中用Tomcat跑一個(gè)Web項(xiàng)目,啟動(dòng)后,Tomcat日志在控制臺(tái)打印出來(lái)都是亂碼,初次遇到這個(gè)問(wèn)題真的很棘手,今天小編帶領(lǐng)大家一起看看解決方法
    2018-07-07

最新評(píng)論

晴隆县| 柳江县| 嘉鱼县| 绥宁县| 静安区| 辛集市| 乐业县| 惠来县| 濮阳县| 蛟河市| 朔州市| 龙山县| 鲜城| 枣庄市| 临夏市| 沾益县| 饶阳县| 兴国县| 砚山县| 苗栗县| 天气| 东海县| 余干县| 商都县| 陆河县| 漾濞| 扶沟县| 广饶县| 上饶市| 德昌县| 卓尼县| 堆龙德庆县| 永吉县| 杭锦旗| 彰化县| 花莲县| 屯门区| 武胜县| 保靖县| 绩溪县| 和顺县|