【多線程5】面試常考鎖知識點

文章目錄

  • 悲觀/樂觀鎖
  • 掛起等待鎖/自旋鎖
  • 偏向鎖
  • 輕量級/重量級鎖
  • 鎖升級
  • CAS
    • CAS引發的ABA問題
      • 解決方案
    • 原子類
  • 公平/不公平鎖
  • 可重入鎖
    • ReentrantLock
        • 讀寫鎖
  • Callable接口

這里的“悲觀”“樂觀”“掛起等待”“自旋”“輕量級”“重量級”“公平”“非公平”“可重入”僅代表某個鎖的特性,不具體指某個鎖。

悲觀/樂觀鎖

悲觀鎖:總是在考慮最壞的情況,認為在拿數據時總是存在線程不安全問題,總是認為別人在同時修改這個數據,于是每次拿的時候給這個數據上鎖,讓其他線程拿不了直到鎖釋放。代表有synchronized、ReentrantLock。(重量級鎖有悲觀鎖的影子!)
樂觀:總是考慮最好的情況,認為總是不存在有線程在同時修改的情況,放心的去拿數據,但是樂觀鎖有CAS的機制,如果要進行更新操作,發現讀取到更新之間有其他線程修改了這個數據,則會放棄修改,重新讀取再修改直到成功。代表有原子類,原子類底層就是不斷的CAS。(輕量級鎖、偏向鎖都有樂觀鎖的影子!)

掛起等待鎖/自旋鎖

自旋鎖:線程未獲得鎖則不斷嘗試獲取鎖。可以及時獲得鎖,CPU空轉(忙等),但缺點是可能會很消耗CPU資源,因此自旋到一定條件(設定時間、次數)就不自旋了。

while(搶鎖失敗){;if(!自旋條件) break;
}

掛起等待鎖:線程未獲得鎖則進入阻塞狀態,等待被喚醒。不會很消耗CPU資源,但缺點是等待周期可能會很長,不能及時獲得鎖。

偏向鎖

當同步代碼塊只有一個線程運行時(可以理解為只有一個線程參與鎖競爭),此時并不會給這個線程加鎖,而是給這個線程一個標記ID(由JVM記錄,這個機制也實現了Java的可重入鎖,方便理解可參考我寫的解決死鎖段落),當下一次進入同步代碼塊時,根據這個ID判斷是不是仍然還是這個線程,如果是則直接運行,否則偏向鎖升級為輕量級鎖,鎖持有者為獲取鎖的線程,其他線程自旋。
上面的過程有點抽象,總結來說:只有一個線程執行同步代碼塊的時候,此刻加的鎖為偏向鎖,如果發生其他線程(同時競爭不激烈)來競爭鎖,則鎖升級為輕量級鎖,如果競爭再激烈,則升級為重量級鎖。

輕量級/重量級鎖

輕量級鎖:鎖競爭不激烈的場景下,線程未獲得鎖則保持自旋(不阻塞等待一直嘗試獲取鎖)。
重量級鎖:鎖競爭激烈的場景下,線程未競到鎖則進入阻塞狀態,等待被喚醒,典型:synchronized

鎖升級

Java的synchronized有鎖升級的機制:

在這里插入圖片描述
synchronized自適應進行升級的過程,保證了JVM不盲目加鎖浪費資源,在鎖競爭緩和的情況下線程不阻塞浪費時間,及時獲取到鎖,在鎖競爭激烈的情況下,讓線程阻塞減輕CPU負擔。

CAS

CAS(Compare And Swap)。顧名思義先比較再交換,CAS涉及到三個數據,這里可以理解為value(修改前讀取到的值;主內存中的共享變量)、exceptValue(上一次修改后保存的值;線程里的局部變量)、swapValue(要修改的值;線程里的局部變量)。定義為exceptValue是因為CAS期待exceptValue修改為swapValue。
CAS的工作機制:

  • 讀取內存值(value)。
  • 比較 value 和exceptValue。
  • 如果相等,則寫入 swapValue,否則CAS失敗。

這三步操作是不可分割的也就是說一次CAS是原子性的
exceptValue的值在CAS成功后被更新為value,否則保持原值不變。

CAS引發的ABA問題

假設原值為A,要修改為B。如果存在多個線程要執行這個修改操作:

  • 一個線程修改成功后,由于緩存一致性協議,value變化時,其他線程已經讀取的value也會被強制刷新為最新值。多個線程進行CAS(A,A,B)時,一個成功后,其他的線程會CAS失敗,不會對A重復CAS(A,A,B)。
  • 但是CAS只檢查值,不關心在修改這個值之前是否發生了A->B->…->A這種騷操作。由此引發了ABA問題。例如:

線程t1要將A修改為B,而線程t2要將A修改為B,再修改為A;
如果t2先執行完這兩個CAS操作,t1由于緩存一致性協議會實時讀取到最新值,所以t1在CAS前里面的A變為B再變為A,t1執行CAS,兩次線程結束后最終結果為B。

雖然上面這ABA操作看起來沒問題,但極端情況下卻容易出問題:

我有200塊存款,要取100塊錢;
我來到ATM,插卡輸密碼,設置取100塊再點擊確定,恰好系統超時沒有及時吐錢,我惱怒的再摁了一下,由此產生了兩個線程t1,t2。兩個線程都要CAS(200,200,100),正常情況下,總有一個成功而另一個失敗,但是又恰好老媽打給我100塊生活費,產生線程t3,在t2完成CAS后,t3又來一波CAS(100,100,200)加了100塊,沒有t3,t1應該是CAS(100,200,100)但現在成了CAS(200,200,100),于是ATM再吐100塊。
這是不符合實際的,現實生活即便手癢快速摁幾下,也不會重復響應。

解決方案

為此基于CAS只比較新舊值的特性引入了版本號,版本號是一個單調遞增的常量,每次CAS就+1,這樣即便A->B->A也能因為版本號而察覺到。

原子類

針對多線程操作共享變量例如例如變量自增這種而不發生線程不安全問題,可以使用原子類。原子類底層使用了CAS,可以保證變量操作的原子性,常見的原子類有AtomicInteger、AtomicIntegerArray、AtomicBoolean、AtomicLong、AtomicReference、AtomicStampedReference。舉例AtomicInteger:

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;public class Main {public static void main(String[] args) {AtomicInteger i = new AtomicInteger();i.addAndGet(1); //對于int/Integer i,相當于i+=1System.out.println(i);i.decrementAndGet();//相當于--iSystem.out.println(i);i.getAndDecrement();//相當于i--System.out.println(i);i.incrementAndGet();//相當于++iSystem.out.println(i);i.getAndIncrement();//相當于i++System.out.println(i);}
}

公平/不公平鎖

Java對公平鎖的定義是先來后到,哪個線程先搶鎖就哪個線程拿鎖,所以非公平鎖就是所有鎖競爭者獲得鎖的機會均等(操作系統隨機調度線程)。

可重入鎖

一個線程執行到同步代碼塊給自己加了鎖,在這個代碼塊里又給自己加了鎖,造成了死鎖,這個線程就自己阻塞自己,鎖也釋放不了。如果允許線程給自己多次加鎖,但又不會發生死鎖,同步代碼塊執行完后鎖正常釋放,那就是可重入鎖

學Java的同學不用擔心死鎖的問題,只要以Reentrant開頭命名的鎖都是可重入鎖,而且JDK提供的所有現成的Lock實現類,包括synchronized關鍵字鎖都是可重入的。

ReentrantLock

和synchronized同級別,是在Java5以后引入的,相比于synchronized對鎖的使用更靈活,也更復雜。以下是同synchronized的對比:

synchronizedReentrantLock
實現方式synchronzied是關鍵字,實現機制在JVM內部,需要手動解鎖ReentrantLock是Java.util.concurrent.locks.ReentrantLock里的類,是在JVM外部實現的,而ReentrantLock需要手動unlock()解鎖
鎖競爭處理線程鎖競爭失敗會一直阻塞不會阻塞;可以通過trylock(TIME)/trylock()返回一個boolean值,如果是false代表競爭鎖失敗,調用者可根據這個判斷編寫競爭失敗的代碼邏輯
鎖特性非公平鎖默認非公平鎖,可通過構造方法傳入ture成為公平鎖
等待和喚醒可通過Object類的wait()、notify()進行等待和喚醒,但喚醒目標是隨機的可通過Condition類指定喚醒某個線程

常用方法lock()trylock(Time)unclock()

import java.util.concurrent.locks.ReentrantLock;public class Main {public static void main(String[] args) {ReentrantLock locker=new ReentrantLock();Thread t1=new Thread(()->{for(int i=0; i<500; i++){try{locker.lock();System.out.println(Thread .currentThread().getName()+i);}finally{locker.unlock();//未防止忘記釋放鎖,將其放在finally里}}});t1.start();}
}
讀寫鎖

多個線程對共享變量的讀取是不會發生線程不安全的的,如果有寫入的情況才會發生:

  1. 所有線程只讀,線程安全
  2. 所有線程寫入,線程不安全
  3. 有讀有寫,線程不安全

當有多個線程操作一個共享變量時,對其加鎖且對它的讀取是不互斥,但是寫入時只能一個線程持有鎖,達到:

  1. 讀與讀之間不互斥
  2. 所有線程寫入,一個線程持有鎖其他阻塞
  3. 有讀有寫,一個線程持有鎖其他阻塞

于是我們引入了讀寫鎖ReentrantReadWriteLock類,通過ReentrantReadWriteLock.readLock獲取一個讀鎖ReadLock對象;通過ReentrantReadWriteLock.writeLock獲取一個寫鎖WriteLock對象。
這兩個對象使用lock()tryLock()、**unlock()**來加鎖釋放鎖。

import java.util.concurrent.locks.ReentrantReadWriteLock;public class Main {static int i=0;public static void main(String[] args) {ReentrantReadWriteLock locker=new ReentrantReadWriteLock();Thread t1=new Thread(()->{locker.readLock().lock();//readLock是ReentrantReadWriteLock類里RreadLock類型的字段System.out.println(i);locker.readLock().unlock();locker.writeLock().lock();//writeLock是ReentrantReadWriteLock類里WriteLock類型的字段i+=1;locker.writeLock().unlock();});t1.start();}
}

Callable接口

Callable類似于Runnable接口,里面定義了call()方法,同run()方法一樣對任務進行了包裝。
但不同的是Callable接口帶有泛型,且call()方法帶有返回值,且不可直接將實現了Callable接口的類對象作為參數直接傳入Thread類的構造方法里,需要通過FutureTask類將任務提交到線程里。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class Main {public static void main(String[] args) throws InterruptedException, ExecutionException {//返回一個實現了Callable接口的匿名類對象Callable<Integer> callable = (() -> {int sum = 0;for (int i = 1; i <= 100; i++) {sum += i;}return sum;});FutureTask<Integer> futureTask = new FutureTask<>(callable);//FutureTask實現了RunnableFuture接口,RunnableFuture繼承了Runnable接口Thread t = new Thread(futureTask);t.start();System.out.println(futureTask.get());}
}

如果要實現上面同樣的功能,還需在Main里定義一個static字段類記錄sum的值方便任務執行完后來確定任務效果。而通過call()帶返回值的屬性和new FutureTask.get()方法得到任務結果,將任務和線程分開,達到高內聚低耦合的目的。

完。

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

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

相關文章

第三屆世界科學智能大賽新能源賽道:新能源發電功率預測-數據處理心得體會1

看懂數據 比賽數據說明&#xff1a; 文檔&#xff08;報名之后可以下載&#xff09;大小操作初賽測試集.zip94MB下載初賽訓練集.zip632MB下載output.zip145KB下載 任務和主題 AI新能源功率預報&#xff1a;根據歷史發電功率數據和對應時段多類別氣象預測數據&#xff0c;實…

【云馨AI-大模型】2025年4月第三周AI領域全景觀察:硬件革命、生態博弈與國產化突圍

一、硬件算力突破點燃多智能體時代 谷歌在4月12日Cloud Next大會發布第七代TPU Ironwood&#xff0c;單芯片算力達4614 TFLOPs&#xff0c;較前代內存提升6倍&#xff0c;專為AI推理場景優化。配合發布的Gemini 2.5 Flash模型通過"思考"功能實現成本優化&#xff0c…

第3章 垃圾收集器與內存分配策略《深入理解Java虛擬機:JVM高級特性與最佳實踐(第3版)》

第3章 垃圾收集器與內存分配策略 3.2 對象已死 Java世界中的所有對象實例&#xff0c;垃圾收集器進行回收前就是確定對象哪些是活著的&#xff0c;哪些已經死去。 3.2.1 引用計數算法 常見的回答是&#xff1a;給對象中添加一個引用計數器&#xff0c;有地方引用&#xff0…

超詳細VMware虛擬機擴容磁盤容量-無坑版

1.環境&#xff1a; 虛擬機&#xff1a;VMware Workstation 17 Pro-17.5.2 Linux系統&#xff1a;Ubuntu 22.04 LTS 2.硬盤容量 虛擬機當前硬盤容量180G -> 擴展至 300G 3.操作步驟 &#xff08;1&#xff09;在虛擬機關機的狀態下&#xff0c;虛擬機硬盤擴容之前必…

HarmonyOS:1.4 - HarmonyOS應用程序框架基礎

判斷題 1.在基于Stage模型開發的應用項目中都存在一個app.json5配置文件、以及一個或多個module.json5配置文件。 正確(True) 2.一個應用只可以包含一個UIAbility組件。 錯誤(False) 3.Background狀態在UIAbility實例銷毀時觸發。可以在onDestroy()回調中進行系統資源的釋…

HTTP HTTPS RSA

推薦閱讀 小林coding HTTP篇 文章目錄 HTTP 80HTTP 響應碼1xx&#xff1a;信息性狀態碼&#xff08;Informational&#xff09;2xx&#xff1a;成功狀態碼&#xff08;Success&#xff09;3xx&#xff1a;重定向狀態碼&#xff08;Redirection&#xff09;4xx&#xff1a;客戶端…

ORACLE數據庫轉國產阿里OceanBase數據庫

1.BLOB類型修改 將接口內oracle.sql.BLOB改為java.sql.Blob 2.REGEXP_LIKE 判斷函數正則表達式中字符轉義問題 OB的正則表達式使用的是標準的Linux模式,oracle是黑盒子,在處理部分轉義符([])的時候, Oracle無需使用轉義符,OB務必使用轉義符,加/轉義處理,例如在regexp_like(t…

STM32的三種啟動方式

目錄 一、從主閃存存儲器啟動&#xff08;Main Flash Memory&#xff09; 二、從系統存儲器啟動&#xff08;System Memory&#xff09; 三、從內置SRAM啟動&#xff08;Embedded SRAM&#xff09; 一、從主閃存存儲器啟動&#xff08;Main Flash Memory&#xff09; >&g…

Flutter使用flutter_driver進行自動化測試

Flutter自動化測試實踐指南 作為一名iOS開發者&#xff0c;我最近對Flutter的自動化測試產生了濃厚興趣。在開發過程中&#xff0c;我發現自動化測試對于保證應用質量至關重要&#xff0c;特別是像我們這樣的創業團隊&#xff0c;測試資源有限的情況下。 搭建Flutter自動化測…

Halcon應用:九點標定-手眼標定

提示&#xff1a;若沒有查找的算子&#xff0c;可以評論區留言&#xff0c;會盡快更新 Halcon應用&#xff1a;九點標定-手眼標定 前言一、Halcon應用&#xff1f;二、應用實戰1、圖形理解[eye-to-hand]&#xff1a;1.1、開始應用2、 圖形理解[eye-in-hand]2.1、 開始應用 前言…

【C++11】列表初始化、右值引用、完美轉發、lambda表達式

&#x1f4da; 博主的專欄 &#x1f427; Linux | &#x1f5a5;? C | &#x1f4ca; 數據結構 | &#x1f4a1;C 算法 | &#x1f310; C 語言 上篇文章&#xff1a;unordered_map、unordered_set底層編寫 下篇文章&#xff1a;C11&#xff1a;新的類功能、模板的可…

Pandas取代Excel?

有人在知乎上提問&#xff1a;為什么大公司不用pandas取代excel&#xff1f; 而且列出了幾個理由&#xff1a;Pandas功能比Excel強大&#xff0c;運行速度更快&#xff0c;Excel除了簡單和可視化界面外&#xff0c;沒有其他更多的優勢。 有個可怕的現實是&#xff0c;對比Exce…

Vue 3 中將 ref 創建的響應式對象數據轉換為普通(非響應式)的數據

Vue 3 中使用 ref 創建的響應式對象數據轉換為普通&#xff08;非響應式&#xff09;的數據&#xff0c;有以下幾種方法&#xff1a; 1. 訪問 .value 屬性: 這是最直接、最常見的方法。 由于 ref 對象的值存儲在其 .value 屬性中&#xff0c;直接訪問該屬性即可獲得普通數據。…

四月下旬系列

CUHKSZ 校賽 期中考試 DAY -1。 省流&#xff1a;前 1h 切 6 題&#xff0c;后 3h 過 1 題&#xff0c;讀錯一個本來很【】的題&#xff0c;被大模擬構造創【】了。 本地除了 VSCode 沒有 Extensions&#xff0c;別的和省選差不多。使用 DEVC。 前 6 題難度 < 綠&#x…

下采樣(Downsampling)

目錄 1. 下采樣的定義與作用?? ??2. 常見下采樣方法?? ??(1) 池化&#xff08;Pooling&#xff09;?? ??(2) 跨步卷積&#xff08;Strided Convolution&#xff09;?? ??(3) 空間金字塔池化&#xff08;SPP&#xff09;?? ??3. PyTorch 實現示例?? …

lottie深入玩法

A、json文件和圖片資源分開 delete 是json資源名字 /res/lottie/delete_anim_images是圖片資源文件夾路徑 JSON 中引用的圖片名&#xff0c;必須與實際圖片文件名一致 B、json文件和圖片資源分開&#xff0c;并且圖片加載不固定 比如我有7張圖片&#xff0c;分別命名1~7&…

高精度算法(加、減、乘、除、階乘和)?

歸納編程學習的感悟&#xff0c; 記錄奮斗路上的點滴&#xff0c; 希望能幫到一樣刻苦的你&#xff01; 如有不足歡迎指正&#xff01; 共同學習交流&#xff01; &#x1f30e;歡迎各位→點贊 &#x1f44d; 收藏? 留言?&#x1f4dd; 唯有主動付出&#xff0c;才有豐富的果…

探索大語言模型(LLM):馬爾可夫鏈——從詩歌分析到人工智能的數學工具

提出背景與靈感起源 馬爾可夫鏈由俄國數學家安德雷馬爾可夫于1906年提出&#xff0c;最初是為了挑戰當時概率論中“獨立性假設”的局限性。他希望通過研究相依變量序列&#xff0c;證明即使隨機變量之間存在依賴關系&#xff0c;大數定律和中心極限定理仍然成立。 靈感來源&am…

【web服務_負載均衡Nginx】三、Nginx 實踐應用與高級配置技巧

一、Nginx 在 Web 服務器場景中的深度應用? 1.1 靜態網站部署與優化? 在 CentOS 7 系統中&#xff0c;使用 Nginx 部署靜態網站是最基礎也最常見的應用場景。首先&#xff0c;準備網站文件&#xff0c;在/var/www/html目錄下創建index.html文件&#xff1a; sudo mkdir -p…

C語言格式化輸入輸出總結 (printf和scanf)

一、printf格式化輸出 1. 整數格式化 (%d, %i, %u, %o, %x) c復制代碼 int num 42; // 以下為不同格式輸出示例 printf("%d", num); // 42 (十進制) printf("%i", num); // 42 (同%d) printf("%u", num); // 42 (無符號十進制…