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

淺談關(guān)于Java中TimeZone鎖競爭引發(fā)的問題解決

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

背景

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

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

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

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
  • 等待對象: java.lang.Class@37b99a71(類鎖)
  • 調(diào)用來源: Joda-Time 的 DateTimeZone.toTimeZone() 方法

問題分析

JDK TimeZone.getTimeZone 的鎖機制

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

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

這意味著所有調(diào)用該方法的線程都需要競爭同一把類鎖。

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

分析 Joda-Time 源碼,DateTime.toGregorianCalendar() 的實現(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)用都會執(zhí)行 zone.toTimeZone(),最終觸發(fā) TimeZone.getTimeZone()。

問題根因

┌─────────────────────────────────────────────────────────────────┐
│                      高并發(fā)請求                                  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│   Thread-1    Thread-2    Thread-3    ...    Thread-N           │
│      │           │           │                   │              │
│      ▼           ▼           ▼                   ▼              │
│  toCalendar()  toCalendar()  toCalendar()   toCalendar()        │
│      │           │           │                   │              │
│      ▼           ▼           ▼                   ▼              │
│  toGregorianCalendar() ────────────────────────────────────     │
│      │           │           │                   │              │
│      ▼           ▼           ▼                   ▼              │
│  toTimeZone() ─────────────────────────────────────────────     │
│      │           │           │                   │              │
│      ▼           ▼           ▼                   ▼              │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │     TimeZone.getTimeZone() - synchronized 類鎖           │   │
│  │                                                          │   │
│  │   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)        │   │
│  └──────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

核心問題

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

原始實現(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;
    }
    // 問題所在:每次都調(diào)用 toGregorianCalendar()
    result = dateTime.toGregorianCalendar();
    return result;
}

優(yōu)化方案

設計思路

由于底層API在項目中頻繁使用,全部改動風險比較大,我也不想因為出錯而背鍋,所以考慮消除鎖競爭的方案。

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

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

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

選擇 Caffeine 的原因:

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

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

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

/**
 * DateTimeZone -> TimeZone 緩存
 * 生產(chǎn)環(huán)境時區(qū)種類有限(通常 < 5 種),設置 maximumSize = 24 足夠
 */
static LoadingCache<DateTimeZone, TimeZone> zoneCache = Caffeine.newBuilder()
        .maximumSize(24)
        .evictionListener((k, v, c) -> {
            // 正常情況不應該觸發(fā)驅(qū)逐,如果觸發(fā)說明時區(qū)種類異常多
            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,避免鎖競爭
    DateTimeZone zone = dateTime.getZone();
    TimeZone timeZone = getTimeZoneFromCache(zone);
    GregorianCalendar cal = new GregorianCalendar(timeZone);
    cal.setTime(dateTime.toDate());
    return cal;
}

優(yōu)化前后對比

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

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

關(guān)鍵設計決策

1. 為什么設置 maximumSize = 24

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

2. evictionListener 的作用

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

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

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

總結(jié)

通過引入 Caffeine 緩存,我們成功消除了 TimeZone.getTimeZone() 的鎖競爭問題。這個優(yōu)化的核心思想是:對于數(shù)量有限、創(chuàng)建成本高的對象,使用緩存來換取并發(fā)性能。

不要使用過時的API。

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

您可能感興趣的文章:

相關(guān)文章

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

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

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

    JavaWeb項目中JSP訪問的問題解決

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

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

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

    解讀CompletableFuture的底層原理

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

    解決idea每次新建項目都需要重新指定maven目錄

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

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

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

    Mybatis邏輯分頁與物理分頁PageHelper使用解析

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

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

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

    Spring Cache 多租戶緩存隔離解決方案實踐

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

    解決IDEA 啟動Tomcat控制臺亂碼問題

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

最新評論

吴旗县| 兴安盟| 镇雄县| 石屏县| 裕民县| 增城市| 乾安县| 改则县| 大新县| 巴林左旗| 宁蒗| 郸城县| 名山县| 达日县| 巨野县| 庆阳市| 仪征市| 修文县| 肥西县| 西平县| 邵阳市| 那曲县| 武功县| 塘沽区| 朔州市| 樟树市| 武汉市| 洪湖市| 北安市| 民乐县| 太白县| 南康市| 塔河县| 湾仔区| 三河市| 遂宁市| 东丰县| 蒙阴县| 宝坻区| 绍兴市| 哈密市|