Java多線程進階-死鎖與面試題解析

文章目錄

  • Java多線程進階:死鎖與面試題解析
    • 一、并發編程的噩夢——死鎖
      • 1. 什么是死鎖?四個缺一不可的條件
      • 2. 如何避免死鎖?從破壞循環等待開始
    • 二、并發編程面試題全景解析
      • 1. 鎖與同步機制
      • 2. CAS 與原子操作
      • 3. JUC 工具與線程池
      • 4. 線程安全集合
      • 5. 綜合問題
    • 本文核心要點總結 (Key Takeaways)

Java多線程進階:死鎖與面試題解析

學到這里,我們總算來到了 Java 多線程學習之旅的最后一站。在前面的筆記里,我們一起探索了鎖策略、synchronized 的底層細節、JUC 工具包以及各種并發集合。現在,是時候挑戰兩個“終極 BOSS”了:

  1. 死鎖:并發編程中最讓人頭疼的噩夢。
  2. 面試題:檢驗我們學習成果的試金石。

這篇筆記的目標很明確:首先,徹底搞懂死鎖的成因和避免策略;然后,整理一份相對全面的并發面試題庫。這既是對整個系列知識的最終鞏固,也希望能幫助自己(以及其他可能讀到這篇筆記的朋友)更自信地應對面試的挑戰。


一、并發編程的噩夢——死鎖

1. 什么是死鎖?四個缺一不可的條件

死鎖,簡單來說,就是多個線程同時被阻塞,它們中的一個或全部都在等待對方持有的資源。因為大家都在等對方先放手,結果就導致所有線程都被無限期地阻塞,程序也就卡住無法正常結束了。

這里我的理解是,可以用一個“吃餃子”的場景來幫助消化這個概念:

滑稽老哥和不吃香菜一起去吃餃子,但桌上只有一瓶醬油和一瓶醋。

  • 滑稽老哥手快,先拿起了醬油瓶(這相當于持有了鎖 A)。
  • 與此同時,不吃香菜拿起了醋瓶(持有了鎖 B)。

接下來,滑稽老哥想蘸醋(請求鎖 B),而不吃香菜想用醬油(請求鎖 A)。如果兩人誰也不肯先把自己的瓶子放下給對方用,那就僵持住了——這就是一個典型的死鎖。

從這個例子可以總結出,死鎖的產生必須同時滿足以下四個缺一不可的條件:

  1. 互斥使用:一個資源一次只能被一個線程使用(醬油瓶一次只能一人拿)。
  2. 不可搶占:線程不能強行從另一個線程手中奪取資源,只能等待其主動釋放(不能從對方手里搶瓶子)。
  3. 請求和保持:線程在持有至少一個資源的同時,又去請求其他資源(拿著醬油瓶,又想要醋瓶)。
  4. 循環等待:存在一個線程等待鏈,T1等T2,T2等T3,…,Tn等T1,形成環路(滑稽老哥等不吃香菜,不吃香菜又在等滑稽老哥)。

2. 如何避免死鎖?從破壞循環等待開始

要避免死鎖,理論上只需破壞上述四個條件中的任意一個即可。但在實際編程中,我們最容易、也最常入手的是破壞“循環等待”條件。

最常用的一種死鎖預防技術就是鎖排序。這個方法思路很簡單:假設有 N 個線程嘗試獲取 M 把鎖, 我們可以對這 M 把鎖進行統一編號 (例如根據它們的 hashCode)。然后定下規矩,所有線程在需要獲取多把鎖時,都必須嚴格按照固定的、從小到大的編號順序來獲取。這樣一來,請求鎖的方向都是一致的,就從根本上避免了形成等待環路。

可能產生死lock的代碼:
下面這個例子很典型,兩個線程以相反的順序申請鎖,就可能導致死鎖。

Object lock1 = new Object();
Object lock2 = new Object();// 線程1: 嘗試先鎖 lock1,再鎖 lock2
new Thread(() -> {synchronized (lock1) {System.out.println("線程1 持有 lock1, 嘗試獲取 lock2...");try { Thread.sleep(100); } catch (InterruptedException e) {}synchronized (lock2) {System.out.println("線程1 成功獲取兩把鎖");}}
}).start();// 線程2: 嘗試先鎖 lock2,再鎖 lock1 (順序相反,非常危險!)
new Thread(() -> {synchronized (lock2) {System.out.println("線程2 持有 lock2, 嘗試獲取 lock1...");synchronized (lock1) {System.out.println("線程2 成功獲取兩把鎖");}}
}).start();

破壞循環等待后的安全代碼:
只要我們約定好,所有線程都先獲取 lock1, 再獲取 lock2,問題就解決了。

Object lock1 = new Object();
Object lock2 = new Object();// 兩個線程都遵循先鎖1再鎖2的順序
new Thread(() -> {synchronized (lock1) {synchronized (lock2) {System.out.println("線程1 成功");}}
}).start();new Thread(() -> {synchronized (lock1) {synchronized (lock2) {System.out.println("線程2 成功");}}
}).start();

二、并發編程面試題全景解析

這部分內容既是對整個多線程系列學習的復習,也是對常見面試問題的檢驗。

1. 鎖與同步機制

1. 如何理解樂觀鎖和悲觀鎖?具體如何實現?

  • 悲觀鎖:我的理解是,它非常“悲觀”,總是假設會發生并發沖突。所以,在每次對數據進行操作前,它都會先加鎖,確保在自己操作的這段時間里,別人碰不了數據。這種方式的實現,通常依賴于底層的同步機制,比如 Java 中的 synchronized 關鍵字和 ReentrantLock 類。
  • 樂觀鎖:它則非常“樂觀”,總是假設不會發生并發沖突。所以,它操作數據時不加鎖,而是在準備提交更新的時候,才去檢查數據在操作期間有沒有被其他線程修改過。如果沒被改過,就成功更新;如果被改了,就放棄或者重試。它的典型實現是 CAS(Compare-and-Swap)機制,為了防止 ABA 問題,通常還會配合一個版本號字段來一起檢查。

2. 介紹一下讀寫鎖?

讀寫鎖(ReadWriteLock)是一種很巧妙的鎖,它把鎖的功能一分為二:分為“讀鎖”和“寫鎖”,特別適合用在“讀多寫少”的場景。

  • 讀鎖 vs 讀鎖:不互斥。大家都是讀數據,不會有沖突,所以允許多個線程同時持有讀鎖,并發讀取。
  • 寫鎖 vs 寫鎖:互斥。為了保證數據寫入的原子性,一次只能有一個線程持有寫鎖。
  • 讀鎖 vs 寫鎖:互斥。當有線程在寫數據時,其他線程不能讀,反之亦然。這保證了讀線程不會讀到修改了一半的“臟數據”。
    Java 中的 ReentrantReadWriteLock 就是它的標準實現。

3. 什么是自旋鎖?為什么要使用它,缺點是什么?

  • 什么是自旋鎖:當一個線程想獲取鎖但發現鎖被占用了,它不會立刻放棄 CPU 進入阻塞狀態,而是在原地進行一個忙等待的循環(“自旋”),不斷地嘗試獲取鎖。
  • 為什么使用:它基于一個假設——鎖被占用的時間通常很短。如果這個假設成立,那么通過自旋等待,線程可以避免進入和退出阻塞狀態的巨大開銷(這涉及到用戶態和內核態的切換以及線程調度),一旦鎖被釋放,就能立刻搶到,響應速度更快。
  • 缺點:如果鎖被占用的時間很長,自旋的線程就會持續空轉,白白浪費 CPU 資源。所以它是個雙刃劍,用對地方才高效。

4. synchronized 是可重入鎖嗎?

是的,必須是。可重入鎖(也叫遞歸鎖)指的是,同一個線程在已經持有鎖的情況下,可以再次成功獲取該鎖,而不會自己把自己鎖死。

它的內部實現機制大致是:鎖本身會記錄下當前是哪個線程持有著它,并且還有一個計數器。當這個線程再次請求這把鎖時,計數器會遞增。每次執行完一次同步代碼塊釋放鎖時,計數器會遞減。只有當計數器減到 0 時,這個鎖才會被真正釋放,其他線程才能獲取。

5. 什么是偏向鎖?

偏向鎖是 JVM 對 synchronized 的一種極致優化。它的核心思想是,在大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一個線程多次獲得。

為了處理這種情況,偏向鎖并不會真的加鎖,而是在對象頭里記錄一個“偏向”的線程 ID。如果后續訪問該鎖的始終是這一個線程,那么它進出同步塊時就幾乎沒有任何開銷,連 CAS 操作都不需要。這大大降低了無競爭情況下的同步成本。當然,一旦有其他線程參與競爭,偏向鎖狀態就會被撤銷,升級為輕量級鎖。

6. synchronized 的實現原理是什么?

synchronized 的實現核心是一個從偏向鎖、輕量級鎖到重量級鎖的鎖升級過程,這是 JVM 為了在不同競爭情況下都能有較好性能而做的優化。

  1. 偏向鎖:當沒有競爭時,鎖會“偏向”于第一個獲取它的線程。此時,僅在對象頭記錄線程ID,開銷最低。
  2. 輕量級鎖:當出現另一個線程競爭時,偏向鎖會升級為輕量級鎖。這時,線程會通過自旋+CAS的方式嘗試獲取鎖,避免了線程阻塞帶來的開銷。
  3. 重量級鎖:如果自旋一定次數后,競爭依然激烈,鎖就會“膨脹”為重量級鎖。此時它會依賴操作系統的 mutex 互斥量來實現。獲取不到鎖的線程會被掛起,進入等待隊列,等待操作系統喚醒。

除此之外,JVM 還會進行鎖消除(如果判斷一段代碼不可能有共享數據競爭,就直接去掉鎖)和鎖粗化(將多個連續的加解鎖操作合并為一個更大的鎖)等優化。

2. CAS 與原子操作

1. 講解一下你自己理解的 CAS 機制。

我理解的 CAS(Compare-and-Swap,比較并交換)是一種非常底層的原子操作,很多無鎖編程都依賴它。你可以把它想象成一個需要三個參數的指令:

  1. 要操作的內存地址 V
  2. 我預期的這個地址上的舊值 A
  3. 我想要更新成的新值 B

執行這條指令時,CPU 會原子性地做一件事:檢查地址 V 上的當前值是否等于我預期的舊值 A。當且僅當它倆相等時,才會把地址 V 上的值更新為新值 B。整個“比較再更新”的過程是一步完成的,不會被其他線程中斷,這就是它實現原子性的關鍵。

2. ABA問題怎么解決?

ABA 問題是 CAS 的一個經典漏洞。意思是,一個值原來是 A,被其他線程改成了 B,然后又改回了 A。我的 CAS 操作在檢查時,會發現值仍然是 A,就誤以為它沒變過,然后執行更新。

要解決這個問題,光比較值是不夠的,還需要引入一個版本號時間戳。每次更新值的時候,都把版本號加一。這樣,CAS 操作就變成了比較“值 + 版本號”。

  • 原來的流程:A -> B -> A
  • 加入版本號后:(A, v1) -> (B, v2) -> (A, v3)
    當我拿著 (A, v1) 去做 CAS 時,發現現在是 (A, v3),版本號對不上,就知道中間發生過變化,從而避免了錯誤的操作。Java 中的 AtomicStampedReference 類就是用來解決這個問題的。

3. AtomicInteger 的實現原理是什么?

它的核心原理就是基于 CAS 的自旋循環。以 incrementAndGet() 方法為例,它內部并沒有用鎖,而是執行了這樣一個循環:

  1. 讀取:先讀取 AtomicInteger 內部 volatile 修飾的當前值(oldValue)。
  2. 計算:在本地計算出新值(newValue = oldValue + 1)。
  3. 交換 (CAS):調用底層的 compareAndSet(oldValue, newValue) 方法,嘗試原子性地用 newValue 替換 oldValue
  4. 判斷與重試:如果 compareAndSet 返回 true,說明在我計算的這段時間里,沒有其他線程修改過值,更新成功,退出循環。如果返回 false,說明值已經被其他線程改了,我的 oldValue 已經過時了。這時,循環會繼續,重新執行第 1 步,直到 CAS 操作成功為止。

3. JUC 工具與線程池

1. 線程同步的方式有哪些?

我目前學到的主要有這幾種:

  • 最基礎的:使用 synchronized 關鍵字,簡單粗暴。
  • 更靈活的:使用 JUC 包下的 Lock 接口實現,比如 ReentrantLock
  • 控制協作的:使用 JUC 包下的一些同步工具類,比如 Semaphore(信號量)、CountDownLatch(倒計時門閂)等。
  • 無鎖方式:使用原子類,比如 AtomicInteger,通過 CAS 保證單個變量操作的線程安全。

2. 為什么有了 synchronized 還需要 JUC 下的 Lock

因為以 ReentrantLock 為代表的 Lock 接口,提供了 synchronized 不具備的、更高級和更靈活的功能,讓我們可以對鎖進行更精細的控制:

  • 可中斷的鎖獲取lockInterruptibly() 允許在等待鎖的過程中響應中斷。
  • 可限時的鎖獲取tryLock(time, unit) 可以嘗試在指定時間內獲取鎖,超時就放棄,避免死等。
  • 公平性選擇:可以在創建時指定為公平鎖,保證線程先來先得,避免饑餓。
  • 靈活的線程通信:可以綁定多個 Condition 對象,實現對不同條件的線程進行分組等待和精確喚醒,而 synchronized 只有一個等待隊列。

3. 信號量聽說過嗎?之前都用在過哪些場景下?

信號量(Semaphore)我理解它是一個控制“許可證”數量的計數器,用來限制能同時訪問某個特定資源的線程數量。

  • acquire():線程嘗試獲取一個許可證,成功則計數器減一。如果許可證發完了(計數器為0),線程就得阻塞等待。
  • release():線程在用完資源后,釋放一個許可證,計數器加一,可能會喚醒一個正在等待的線程。

它最常見的用途就是流量控制資源池管理。比如,我們有一個數據庫連接池,里面只有10個連接,那就可以用一個初始值為10的 Semaphore 來控制,確保最多只有10個線程能同時拿到連接。

4. 解釋一下 ThreadPoolExecutor 構造方法的參數的含義。

這是創建線程池最核心的構造方法,每個參數都得搞清楚:

  • corePoolSize: 核心線程數。線程池創建后,即使線程是空閑的,也會長期保留這么多線程。
  • maximumPoolSize: 最大線程數。當任務隊列滿了,線程池最多能再創建多少個“臨時”線程,總線程數不能超過這個值。
  • keepAliveTime: 臨時線程的空閑存活時間。當線程數超過 corePoolSize 時,那些多出來的臨時線程如果空閑了這么久還沒接到新任務,就會被銷毀。
  • unit: keepAliveTime 的時間單位。
  • workQueue: 任務阻塞隊列。當核心線程都在忙時,新來的任務會先被存放在這個隊列里排隊。
  • threadFactory: 線程工廠。用來創建新線程,可以自定義線程的名字、是否為守護線程等。
  • handler: 拒絕策略。當任務隊列已滿,并且線程數也達到了 maximumPoolSize 時,用來處理新提交任務的策略(比如拋異常、丟棄任務等)。

5. Java創建線程池的接口是什么?參數LinkedBlockingQueue的作用是什么?

  • 創建線程池的核心接口是 ExecutorService。我們通常不直接實現它,而是通過 Executors 這個工廠類提供的靜態方法(如 newFixedThreadPool)或者直接構造 ThreadPoolExecutor 類的實例來創建。
  • LinkedBlockingQueue 在線程池中通常被用作任務隊列(workQueue)。它的特點是一個無界的鏈式阻塞隊列。當所有核心線程都在忙碌時,新提交的任務就會被無限地添加到這個隊列中,等待核心線程空閑后前來領取并執行。

4. 線程安全集合

1. ConcurrentHashMap的讀是否要加鎖,為什么?

讀操作(get 方法)幾乎不加鎖,這也是它性能高的關鍵之一。

ConcurrentHashMap 為了最大化讀操作的并發性能,采取了非常精巧的設計。它通過將共享變量(比如哈希桶中 Node 節點的 valnext 指針)聲明為 volatile,來利用 volatile 的內存可見性語義。這確保了當一個線程修改了某個節點后,這個修改能立刻對其他讀線程可見。這樣,讀線程總能看到最新的數據,從而實現了高效的無鎖讀取。

2. 介紹下 ConcurrentHashMap的鎖分段技術?

鎖分段技術是 Java 1.7 版本中 ConcurrentHashMap 提高并發度的核心策略。它的思路是,不對整個哈希表加一把大鎖,而是將整個表在邏輯上分成若干個“段”(Segment),每個 Segment 本身就像一個小型的、線程安全的 Hashtable,擁有自己獨立的鎖。

當需要操作某個 key 時,只需根據 key 的哈希值定位到它所屬的那個 Segment,然后只鎖定這一個 Segment 即可,其他 Segment 的操作完全不受影響。這相當于把一把大鎖拆分成了多把小鎖,大大提高了并發寫入的效率。

不過這里有個重點需要記一下:這個技術已經在 Java 1.8 中被優化替換了。在 Java 1.8 及以后的版本中,ConcurrentHashMap 取消了 Segment 的設計,改為使用 synchronized 鎖住哈希桶的頭節點,再加上 CAS 操作來輔助,實現了粒度更細的鎖定,并發性能通常更好。

3. HashtableHashMapConcurrentHashMap 之間的區別?

這三個是面試老朋友了,它們的關鍵區別在于線程安全和性能:

  • HashMap: 線程不安全。如果用在多線程環境下,需要手動在外部加鎖。它的優點是性能最高,并且 key 和 value 都允許為 null
  • Hashtable: 線程安全。它實現安全的方式非常粗暴,就是給幾乎所有 public 方法都加上了 synchronized,鎖住的是整個 Hashtable 對象。這導致并發效率極低,現在基本不推薦使用了。另外,它的 key 和 value 都不允許null
  • ConcurrentHashMap: 線程安全,并且是為高并發場景設計的。在 JDK 1.8 中,它通過 synchronized 鎖住哈希桶的頭節點 + CAS + volatile 變量來保證安全,實現了細粒度的鎖,并發性能非常高。和 Hashtable 一樣,它的 key 和 value 也都不允許null

5. 綜合問題

1. 談談死鎖是什么,如何避免死鎖?

  • 死鎖定義:我理解的死鎖,就是兩個或多個線程在執行過程中,因為爭奪資源而陷入一種互相等待的僵局。如果沒有外力干涉,它們誰也無法繼續往下執行。
  • 四個必要條件:互斥使用、不可搶占、請求與保持、循環等待。這四個條件必須同時滿足才會發生死鎖。
  • 如何避免:最核心的方法就是想辦法破壞這四個必要條件之一。在編程實踐中,我們最常用的手段是通過鎖排序來破壞“循環等待”條件,即規定好所有線程都必須以一個固定的、相同的順序來獲取一系列的鎖。

2. volatile關鍵字的用法?

volatile 關鍵字在我看來主要有兩個關鍵作用:

  1. 保證內存可見性:這是它最核心的功能。它能確保一個線程對 volatile 變量的修改,能夠立刻被其他線程“看到”。底層原理是它會強制線程每次都從主內存中讀取變量的值,而不是依賴自己工作內存中的緩存。
  2. 禁止指令重排序volatile 還能充當一個內存屏障,防止編譯器和處理器為了優化性能而隨意改變代碼的執行順序。這一點在實現某些底層并發算法時至關重要,比如經典的雙重檢查鎖定(DCL)單例模式。

3. Java多線程是如何實現數據共享的?

JVM 的內存模型把內存分成了幾個區域,其中**堆內存(Heap)方法區(Method Area)**是所有線程共享的區域。所以,只要我們把一個對象(無論是類的實例還是靜態變量)創建在這兩個共享區域里,那么這個對象的引用就可以被多個線程同時持有和訪問,這樣就實現了線程間的數據共享。

4. Java線程共有幾種狀態?狀態之間怎么切換的?

根據 Thread.State 枚舉,Java 線程有 6 種狀態:

  • NEW (新建): 線程對象被創建,但還沒調用 start() 方法。
  • RUNNABLE (可運行): 這是個復合狀態,包含了操作系統線程狀態中的“就緒”(Ready)和“運行中”(Running)。調用 start() 后線程就進入這個狀態,等待 CPU 調度。
  • BLOCKED (阻塞): 線程在等待進入一個 synchronized 同步塊時,因為獲取不到監視器鎖而被阻塞。
  • WAITING (無限期等待): 線程需要等待其他線程執行特定的喚醒動作。調用 Object.wait()Thread.join()LockSupport.park() 會進入此狀態。
  • TIMED_WAITING (限時等待): 和 WAITING 類似,但它不會無限等下去,會在指定時間后自動喚醒。調用 Thread.sleep(time)Object.wait(time) 等方法會進入此狀態。
  • TERMINATED (終止): 線程的 run() 方法執行完畢,線程生命周期結束。

5. 在多線程下,如果對一個數進行疊加,該怎么做?

要保證線程安全,主要有兩種方法:

  • 加鎖:最直接的方法,使用 synchronized 關鍵字或 ReentrantLock 來保護這個疊加操作,確保同一時間只有一個線程能執行 i++
  • 使用原子類:更推薦的方式是使用 java.util.concurrent.atomic 包下的原子類,比如 AtomicInteger。它的 addAndGet()incrementAndGet() 方法是基于 CAS 操作實現的,屬于無鎖操作,在并發量大的情況下性能通常比加鎖要好。

6. Servlet是否是線程安全的?

Servlet 本身的設計是單實例多線程的。也就是說,Web 容器(比如 Tomcat)通常只會為每個 Servlet 類創建一個實例。當多個 HTTP 請求同時訪問這個 Servlet 時,容器會為每個請求分配一個獨立的線程,這些線程會并發地執行同一個 Servlet 實例的 service() 方法。

因此,結論是:

  • 如果 Servlet 中定義了成員變量(實例變量),并且 service() 方法對這些成員變量進行了讀寫操作,那么就會存在線程安全問題。
  • 如果 Servlet 中只使用了局部變量(在 service() 方法內部定義的變量),那么它是線程安全的,因為每個線程都有自己獨立的棧空間來存放局部變量。

7. ThreadRunnable的區別和聯系?

  • Runnable 是一個接口,里面只有一個 run() 方法。它代表的是一個“任務”或“要做什么事”。
  • Thread 是一個類,它代表一個執行任務的線程實體,是真正“干活的人”。
  • 聯系: Thread 類本身也實現了 Runnable 接口。創建線程時,可以將一個 Runnable 對象作為任務傳遞給 Thread 的構造函數。
  • 區別與選擇: 推薦使用實現 Runnable 接口的方式。因為它將“任務”和“執行者”解耦了,一個 Runnable 任務可以被不同的 Thread 執行。而如果通過繼承 Thread 類的方式,由于 Java 的單繼承限制,這個類就不能再繼承其他類了,靈活性較差。

8. 多次start一個線程會怎么樣?

一個 Thread 對象的 start() 方法只能被調用一次。

  • 第一次調用 start() 會成功啟動線程,使其進入 RUNNABLE 狀態,并由 JVM 調度執行其 run() 方法。
  • 之后對同一個線程對象再次調用 start(),無論線程是否已經執行完畢,都會拋出 IllegalThreadStateException 異常。

9. 有synchronized兩個方法,兩個線程分別同時調用,請問會發生什么?

這要看 synchronized 修飾的是什么類型的方法,以及兩個線程調用的是否是同一個對象的實例方法。

  • 修飾非靜態方法:此時鎖是當前對象實例 (this)。
    • 如果兩個線程調用的是同一個對象的這兩個同步方法,它們會互斥,一個線程執行時另一個必須等待鎖。
    • 如果兩個線程調用的是不同對象的這兩個同步方法,它們之間沒有關系,不會互斥,可以并發執行。
  • 修飾靜態方法:此時鎖是當前類的 Class 對象 (Xxx.class)。
    • 這種情況下,鎖是全局唯一的。無論兩個線程操作的是不是同一個對象實例,只要它們調用的是這個類的任何靜態 synchronized 方法,都會互斥。

10. 進程和線程的區別?

這是個基礎但非常重要的問題,我的理解是:

  • 資源單位 vs 調度單位:進程是操作系統進行資源分配的最小單位(比如內存空間)。線程是 CPU 調度的最小單位,是真正執行計算的。
  • 包含關系:一個進程可以包含一個或多個線程。線程是進程的一部分,不能獨立存在。
  • 內存共享:進程之間的內存空間是相互獨立的。而同一進程內的所有線程共享該進程的內存空間(如堆、方法區),但每個線程有自己獨立的棧和程序計數器。
  • 開銷:創建、銷毀和切換進程的開銷遠大于線程,因此線程也被稱為“輕量級進程”。

本文核心要點總結 (Key Takeaways)

  • 鎖的本質是權衡:并發編程中的所有鎖策略,都是在性能開銷數據安全之間尋找平衡點。沒有普適的“最優解”,只有“最適合”特定場景的方案。
  • synchronized 是智能的:現代 JVM 中的 synchronized 遠非一個簡單的互斥鎖,它內部集成了偏向鎖、輕量級鎖(自旋)、重量級鎖的動態升級機制,以及鎖消除、鎖粗化等優化,努力在各種場景下都提供接近最優的性能。
  • CAS是無鎖并發的基石:CAS(比較并交換)作為一種 CPU 級別的原子指令,是 JUC 中許多高性能并發類(如 AtomicInteger, ConcurrentHashMap)的實現基礎,它用樂觀的非阻塞方式,在很多場景下替代了傳統的悲觀阻塞式加鎖。
  • JUC是并發編程的瑞士軍刀java.util.concurrent 包提供了一套豐富、模塊化的并發工具(如 ReentrantLock, Semaphore, CountDownLatch, 線程池等),它們是解決復雜并發問題的強大武器庫。
  • 死鎖可防可控:深刻理解死鎖產生的四個必要條件,并在編碼實踐中(最常見的是通過“鎖排序”)主動破壞其中之一,是預防這個嚴重并發問題的關鍵。
    是 JUC 中許多高性能并發類(如 AtomicInteger, ConcurrentHashMap)的實現基礎,它用樂觀的非阻塞方式,在很多場景下替代了傳統的悲觀阻塞式加鎖。
  • JUC是并發編程的瑞士軍刀java.util.concurrent 包提供了一套豐富、模塊化的并發工具(如 ReentrantLock, Semaphore, CountDownLatch, 線程池等),它們是解決復雜并發問題的強大武器庫。
  • 死鎖可防可控:深刻理解死鎖產生的四個必要條件,并在編碼實踐中(最常見的是通過“鎖排序”)主動破壞其中之一,是預防這個嚴重并發問題的關鍵。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/94214.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/94214.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/94214.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ZYNQ啟動流程——ZYNQ學習筆記11

ZYNQ SoC 的啟動由片上的 BootROM 開始。片上 BootROM 是 ZYNQ 芯片上的一塊非易失性存儲器,它包含了 ZYNQ 所支持的配置器件的驅動, 而且里面的代碼是不可修改的。 BootROM 中的代碼首先會在片外的非易失性存儲器中尋找一個頭文件, 頭文件里…

C++利用CerateProcess創建WPF進程并通過命名管道通訊

引言原因是我需要在C程序中調用另外一個WPF窗體打開或則關閉,進程之前通過通訊協議進行交互。由于使用不同語言開發,兩者都比較復雜不方便重寫,最方便的方法就是使用進程間通信,WPF窗體應用程序根據消息進行Show/Hide/Exit操作。函…

Seaborn數據可視化實戰

1. Seaborn基礎與實踐:數據可視化的藝術 2. Seaborn入門:環境搭建與基礎操作 3. Seaborn基礎圖表繪制入門 4. Seaborn數據可視化基礎:從內置數據集到外部數據集的應用 5. Seaborn顏色與樣式定制教程 6. Seaborn數據可視化入門:繪制…

BIM+寫實數字孿生落地實戰指南

🌟 正文 在智慧城市與工業4.0的浪潮中,BIM與數字孿生的深度碰撞正在重塑建筑的生命周期。基于Revit(RVT)模型構建的超寫實數字孿生體,不僅實現物理空間的毫米級鏡像,更通過實時數據驅動,賦予建…

[Git] 如何拉取 GitHub 倉庫的特定子目錄

作為開發者,我們經常遇到只需要克隆大型倉庫中某個子目錄的場景。 Git 本身并不支持直接克隆子目錄,但通過一些技巧可以實現類似效果。本文將介紹幾種實用的方法,幫助獲取目標代碼。 為什么需要局部拉取? 節省時間和帶寬&#xff…

修復Simulink到UE5丟包時被控船體的殘影問題

提問 simulink 有一個和UE5協同的模塊,叫做Simulation 3D Scence Configuration,還有一個發送來自simulink到UE5數據的模塊叫做Simulation 3D Message。 現在遇到的問題是,這兩個模塊的優先級設置是正確的,且sample time都設置為0…

嵌入式第三十五課!!Linux下的網絡編程

一、目的網絡編程的目的實際上也是進程通信的一種方式,不過它可以在不同的主機上進行通信;二、需要解決的問題1. 主機與主機之間物理層面必須互聯互通。指的是參與通信的計算機(主機)需要通過物理設備建立連接(光纖、網…

遙感機器學習入門實戰教程|Sklearn案例⑦:特征選擇與重要性分析

很多同學問:波段/特征一多就“維度災難”,訓練慢、過擬合,且很難解釋“哪些特征最關鍵”。本篇用 sklearn 給出一套能跑、可視化、可比較的最小工作流,并配上方法論速記,幫助你在高光譜/多特征任務里做出穩健篩選。 &a…

地理數據制備:蔚藍地圖空氣質量數據的獲取、清洗與坐標匹配指南

【📊】手把手攻略:如何從“蔚藍地圖”挖寶——獲取濟南市可用空氣質量數據全記錄 一份不需要寫代碼也能搞定環境數據獲取的實用指南 ? 引言:為什么選擇蔚藍地圖? 作為一名環境數據愛好者,我經常需要獲取準確、可靠、…

Unreal Engine USceneComponent

Unreal🏛 Unreal Engine - USceneComponent📚 定義🏷 類繼承? 關鍵特性?? 常見配置🛠? 使用方法🔗 創建與掛載🔄 獲取與修改 Transform🧩 附加/分離組件🏊 典型應用場景&#x1…

2025年9月5090工作站、

在深度學習與大模型訓練領域,算力是決定研發效率與模型性能的核心要素,而顯卡作為算力輸出的核心硬件,其性能參數直接影響著訓練任務的速度、穩定性與成本控制。對于企業與科研機構而言,選擇一套適配自身需求且性價比優異的顯卡及…

亞矩陣云手機:亞馬遜第三方店鋪多賬號安全合規運營的核心技術支撐

亞矩陣云手機在亞馬遜第三方店鋪多賬號安全合規運營的技術支持,通過硬件級虛擬化、AI 行為建模、動態資源調度三大核心技術模塊,構建了覆蓋設備、網絡、行為、數據的四維防御體系,確保賬號在亞馬遜平臺規則下的長期穩定運行。以下從技術架構、…

使用C++11改進工廠方法模式:支持運行時配置的增強實現

在軟件開發中,工廠方法模式是一種常用的設計模式,用于創建對象。通過使用C11的新特性,我們可以進一步改進工廠方法模式,使其更加靈活和高效。本文將詳細介紹如何使用C11的std::function、lambda表達式和智能指針來實現一個支持運行…

小程序插件使用

插件介紹 插件是對一組 js 接口、自定義組件 或頁面的封裝,用于嵌入到小程序中使用。插件不能獨立運行,必須嵌入在其他小程序中才能被用戶使用;而第三方小程序在使用插件時,也無法看到插件的代碼。因此,插件適合用來封…

要區分一張圖片中的網狀圖(如網格結構或規則紋理)和噪點(隨機分布的干擾像素),比如電路的方法 計算機視覺

要區分一張圖片中的網狀圖(如網格結構或規則紋理)和噪點(隨機分布的干擾像素),需結合圖像預處理、特征提取和分割算法。以下是系統化的解決方案,分階段說明關鍵技術和算法選擇: 🔍 一…

06_并發編程高級特性

第6課:并發編程高級特性 課程目標 掌握context包的使用 理解sync包中的同步原語 學會處理并發安全問題 掌握性能優化技巧 1. Context包 1.1 Context基礎 import ("context""fmt""time" )// 基本Context使用 func basicContext()

X00238-非GNSS無人機RGB圖像衛星圖像視覺定位python

獲取方式見文末,可開發票隨著無人機在工業和科研領域應用的加速發展,在非城市環境中使用無gnss、基于視覺的方法進行無人機定位的需求日益增長。本文提出了一種基于視覺的定位算法,利用深度特征計算無人機在野外飛行的地理坐標。該方法基于匹…

Eino 開源框架全景解析 - 以“大模型應用的搭積木指南”方式理解

Eino 開源框架全景解析 - 大模型應用的搭積木指南 🎯 什么是 Eino?一句話概括 Eino 是字節跳動開源的大語言模型應用開發框架,就像是一個專門為 AI 應用設計的"搭積木工具箱",讓開發者能夠像搭樂高一樣輕松構建復雜的 A…

嵌入式開發中,usb通信中輸出端點和輸入端點

一. 簡介本文簡單學習一下,嵌入式開發中,usb的輸出端點和輸入端點。在嵌入式開發的 USB 通信場景中,輸出端點(OUT Endpoint) 和 輸入端點(IN Endpoint) 是 USB 設備與主機(如電腦、嵌…

【自用】Maven常用依賴

【自用】Maven常用依賴 工具類 Guava Guava(Google Guava)是由Google團隊開發的一套Java開源工具庫,旨在簡化和增強Java開發者的日常工作。它提供了許多實用的工具和基礎設施,覆蓋了集合、并發、字符串處理、I/O、數學運算等多個…