多線程進階(Java)

注:此博文為本人學習過程中的筆記

1.常見的鎖策略

當我們需要自己實現一把鎖時,需要關注鎖策略。Java提供的synchronized已經非常好用了,覆蓋了絕大多數的使用場景。此處的鎖策略并不是和Java強相關的,只要涉及到并發編程,涉及到鎖,都要關注鎖策略。鎖策略就是指這個鎖在加鎖的過程中有什么特點,有什么行為

1.樂觀鎖和悲觀鎖

這不是針對某一把具體的鎖,而是一種特性,一把鎖具有“樂觀”或者“悲觀”的特性。

悲觀鎖是指加鎖的時候,預測接下來的鎖競爭非常激烈,針對這樣激烈的情況會做一些額外的工作。

樂觀鎖是指加鎖的時候,預測接下來的鎖競爭不激烈,不做額外的工作。

2.重量級鎖和輕量級鎖

這是指遇到以上場景之后的解決方案

重量級鎖是指當悲觀的場景下付出更多的代價,更低效

輕量級鎖是指當樂觀的場景下付出較少的代價,更高效

3.掛起等待鎖和自旋鎖

掛起等待鎖是重量級鎖的典型實現,是操作系統內核級別的,加鎖的時候發現競爭,就會使線程進入阻塞狀態,后續就需要內核進行喚醒了。競爭激烈,獲取鎖的周期更長,很難及時獲取到鎖,此時這個過程就不會消耗cpu資源,讓cpu去做其他的事情。

自旋鎖是輕量級鎖的典型實現,是應用程序級別的,加鎖的時候發現競爭,一般是不進入阻塞,而是通過忙等的形式進行等待。鎖競爭不激烈,獲取鎖的周期更短,等待鎖的過程中會一直消耗cpu資源。

4.普通互斥鎖和讀寫鎖

synchronized就是普通互斥鎖,只有加鎖和解鎖。而讀寫鎖存在讀方式加鎖,寫方式加鎖和解鎖。多個線程讀取一個數據本身就是線程安全的,但是當遇到多個線程讀數據,而有一個線程寫數據,那么就會涉及到線程安全問題了。當大部分操作在讀,少部分操作在寫,這時把鎖設置成普通互斥鎖就意味著鎖沖突會非常嚴重。讀寫鎖能確保讀鎖和讀鎖之間不互斥,而讀鎖和寫鎖,寫鎖和寫鎖之間產生互斥,在保證線程安全的前提下,減少鎖沖突的概率,提高效率。

Java標準庫中可以使用讀寫鎖,使用的是經典的lock和unlock寫法

5.可重入鎖和不可重入鎖

當一個線程針對一把鎖連續加鎖的時候,可重入鎖不會造成死鎖,synchronized就是可重入鎖。可重入鎖的要點有:1.鎖要記錄當前是哪個線程拿到鎖,2.統計加鎖的次數,在合適的時機釋放鎖。

6.公平鎖和非公平鎖

當多個線程爭取一把鎖的時候,鎖被釋放時,如果遵守先來后到的原則,那么這個鎖就是公平鎖。synchronized是非公平鎖,鎖在默認情況下被獲取到的概率是均等的,因為操作系統的調度是隨機的。要想實現公平鎖需要付出額外的代價,比如用一個隊列來記錄各個線程獲取鎖的順序。

7.synchronized

synchronized是自適應的鎖,不是讀寫鎖,是可重入鎖和非公平鎖。自適應是指,jvm會統計鎖競爭的激烈程度,來決定鎖是掛起等待鎖還是自旋鎖。鎖自適應的過程存在鎖升級,由無鎖升為偏向鎖,再身為自旋鎖,最后升級為掛起等待鎖。偏向鎖就是指當synchronized的時候不是一上來就加鎖,而是加一個標記,如果這個鎖沒有被競爭,就不會真正的加鎖,在解鎖的時候把這個標記刪除即可。如果這個鎖被競爭了,那就會真正加鎖。這個標記是非常輕量的,比加鎖解鎖高效得多。當前jvm只提供了鎖升級,不存在鎖降級。

8.鎖消除

這是編譯器優化的一種體現,編譯器會判定當前這個代碼是否真的需要加鎖,如果確實不需要加鎖,就會自動把synchronized刪去。這個優化的策略是比較保守的,所以我們加鎖的時候還是要仔細辨別。

9.鎖粗化

這里引入一個新的概念,鎖的粒度。當加鎖和解鎖之間包含的代碼越多(需要執行的指令),鎖的粒度就越粗,反之越細。一個代碼中反復針對細粒度的代碼進行加鎖,就可能優化成粗粒度的鎖。因為每次加鎖解鎖都會增加鎖的競爭,影響效率。

2.CAS

CAS是指比較和交換,compare and swap。

boolean CAS(address, expectedValue, swapValue) {if(&address == expectedValue) {address == swapValue;return true;}return false;
}

這里的偽代碼是指判定內存中的值是否和寄存器1的值一致,如果一致就把內存中的值和寄存器2的值進行交換。由于這里基本只關心內存里的值,而不關注寄存器2的值,所以可以把這里理解成賦值,基于交換實現的賦值。?

CAS是cpu的一條指令,所以它是原子的,這就對我們編寫多線程代碼產生了很大的作用。

CAS本質上是cpu的指令,操作系統把這個指令進行了封裝,提供了一些api,就可以在C++被調用了,而jvm又是C++實現的,所以jvm也能實現CAS操作。

1.原子類

CAS主要的應用是原子類。以下是Java標準庫中提供的原子類。原子類是一個專有名詞,特指atomic這個包里的這些類。

對boolean,int,long這些類型進行了封裝,確保性能,又能確保線程安全。?

以AtomicInteger里的getAndIncrement為例(以下是偽代碼)

class AtomicInteger {private int value;public int getAndIncrement() {int oldValue = value;//可以把oldValue理解成寄存器while(CAS(oldValue, value, oldValue + 1) != true) {oldValue = value;}return oldValue;}
}

在計算的過程中,即使是多線程操作,因為CAS會不停對比寄存器和內存里的值,所以不會產生線程安全問題。?

2.基于CAS實現自旋鎖

public class SpinLock {private Thread owner;private void lock() {//通過CAS判斷當前鎖是否被線程持有//如果這個鎖已經被其他線程所持有,那么就自旋等待//如果這個鎖沒有被其他線程持有,那么鎖的擁有者就設為當前線程while(!CAS(this.owner, null, Thread.currentThread)) {}}private void unlock() {this.owner = null;}
}

3.CAS的典型缺陷?

CAS有一個典型的缺陷,是ABA問題。使用CAS能夠進行線程安全的編程的核心就是比較,比較內存和寄存器里的值。這里本質上就是在判定是否有其他線程插入進來進行了修改。我們認為如果內存和寄存器里的值一致,那么就沒有線程穿插進來修改。但實際上存在另一種情況,另一個線程把內存里的A改成B,又把B改回A。

ABA問題一般不會產生什么大問題,只有在極端情況下才會產生嚴重問題。

1.事例

以一個取錢的問題舉例,假設我的余額有1000,我在atm機前想取出500。由于我不當的操作,讓機器里產生了兩個線程來進行取錢操作。即使兩個線程穿插操作,因為CAS有判斷,所以不會產生什么問題。但如果在第一個線程的CAS執行完畢,余額變成500之后,剛好在那個瞬間有人往我的余額里轉了500,那么余額又變成了1000,此時第二個線程進行判斷后,就又會扣500。最終,我的余額就只有500了。

2.解決方法

在上述問題中,是使用余額來判定是否有其他線程插入,余額這個數值是既能增加又能減少,所以會產生ABA問題。如果這個時候我們引入一個其他指標,比如“版本號”,規定它只能增加,不能減少,每進行一次操作時,版本號就加1,就能有效避免ABA問題。

3.JUC相關的組件

這里的JUC指的是,java.util.concurrent這個包。這個包里封裝了和并發編程相關的東西。

1.Callable接口

這個接口和Runnable接口是并列關系。Runnable接口里面的run方法沒有返回值,Callable接口里面的call方法可以設置返回值

public Test {public static void main(String[] args) {Callable<Integer> callable = new Callable<>(){public int call() {int result = 0;for(int i = 0; i < 5000; i++) {result++;}return result;}};//Thread沒有提供參數是Callable的構造方法,所以要借助FutureTask這個類//注意這里的泛型類要和Callable的一致FutureTask<Integer> futureTask = new FutureTask<>(callable);Thread t = new Thread(futureTask);t.start();//get操作就是獲取FutureTask的返回值,這個返回值來自Callable里的call方法//get是可能會阻塞的,如果線程沒有執行完,get拿不到返回結果,那么它就會一直阻塞System.out.println(futureTask.get());}
}

2.ReentrantLock

ReentrantLock和synchronized是并列的,都是用來加鎖的。

1.synchronized是關鍵字(內部是通過C++實現的),ReentrantLock是類(內部是Java代碼實現的)。?

2.synchronized是通過代碼塊來加鎖,ReentrantLock是通過lock和unlock來加鎖的,注意unlock容易掉,搭配finally使用

3.ReentrantLock除了提供lock方法外,還提供了一個tryLock方法,這個方法加鎖不會阻塞,會返回true或者false,由開發者根據判定結果決定后續的操作。

4.ReentrantLock還提供了公平鎖的實現,它默認是非公平鎖,在new的時候往括號里填個true就能獲得一把公平鎖。

5.ReentrantLock搭配的等待通知機制是Condition類,功能比wait/notify更強大

3.Semaphore?

Semaphore指的是信息量,能夠協調多個線程之間的資源分配。

信息量表示的是“可用資源的個數”,申請一個資源(P操作,acquire)時就會+1,釋放一個資源(V操作,release)時就會-1。當計數器為0時,繼續申請就會阻塞等待。

Semaphore semaphore = new Semaphore(需要的信號量);

Semaphore有一種特殊情況,當信號量為1時,取值要么是1要么是0,此時就相當于一把鎖,我們也可以通過信號量的設置來限制最多有幾個線程來執行任務。?

4.CountDownLatch

使用多線程編程時,經常把一個大任務拆分成多個子任務,并發執行這些子任務,從而提高程序的效率。那我們要怎么衡量這些任務都完成了呢?這時就需要用到CountDownLatch。

1.基本使用

1.構造方法指定參數,描述一共有多少個任務

2.每個任務執行完畢之后,都調用countDown方法,當調用次數達到了設定的參數,則全部執行完

3.在主線程中調用await方法,等待所有任務執行完。

4.在多線程環境下使用集合類

我們在數據結構中學到的集合類大多都是線程不安全的

1.多線程下使用ArrayList

1.自行加鎖(推薦)

自己分析清楚哪些部分需要加鎖。

2.Collections synchronized(new ArrayList);

這個東西相當于套了一層殼,返回的所有List里的方法都是synchronized加鎖的。

3.使用CopyOnWrite

這個是編程中常見的一種思想方法,寫時拷貝。

假設我們有一個數組,現在有線程1對它進行修改,那么就會先復制一份這個數組,在復制的數組上進行修改,修改完之后在改變引用的指向。此時如果有其他線程來讀取這個數組,我們能保證要么這個線程讀到的時舊數組,或者是新數組,不會是修改到一半的數組。

缺陷

1.當這個數組非常大時,進行復制的開銷會很大。

2.當有多個線程進行修改操作時,也會產生很大的問題。

這個方法使用于特定的場景,比如服務器重新加載配置的時候。

2.多線程下使用HashMap

HashMap是線程不安全的,雖然HashTable是線程安全的,但是它是給所有方法都加鎖,效率不高。所以我們使用ConcurrentHashMap,它是按照桶級別進行加鎖,不是加了一個全局鎖,大幅降低了產生鎖沖突的概率。

上圖中的豎線標志對應的鏈表。

Concurrent的核心優化點

1.桶級別加鎖

當有多個線程進行修改時,如果修改的是不同鏈表上的值,本身就不涉及線程安全問題。如果在同一個鏈表上修改才會產生線程安全問題。?在實際開發中,使用的哈希表可能是非常大的,那么桶也會有很多,大概率是不會產生線程安全問題的。

2.size使用原子類

修改不同鏈表的過程不會產生線程安全問題,但是它們一起修改哈希表的size時,就會有問題了,這個時候我們就可以使用原子類來設置size

3.分段擴容

當我們想對哈希表進行擴容時,一次把所有的數據搬運完會比較耗時間,這是就可以分段搬運數據,進行多次put/get。

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

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

相關文章

c++STL——stack、queue、priority_queue的模擬實現

文章目錄 stack、queue、priority_queue的模擬實現使用部分模擬實現容器適配器deque的介紹原理真實結構deque的迭代器deque的操作deque的優缺點 stack的模擬實現按需實例化queue的模擬實現priority_queue的模擬實現為何引入仿函數代碼實現 stack、queue、priority_queue的模擬實…

【深度學習—李宏毅教程筆記】Transformer

目錄 一、序列到序列&#xff08;Seq2Seq&#xff09;模型 1、Seq2Seq基本原理 2、Seq2Seq模型的應用 3、Seq2Seq模型還能做什么&#xff1f; 二、Encoder 三、Decoder 1、Decoder 的輸入與輸出 2、Decoder 的結構 3、Non-autoregressive Decoder 四、Encoder 和 De…

C++鐫刻數據密碼的樹之銘文:二叉搜索樹

文章目錄 1.二叉搜索樹的概念2.二叉搜索樹的實現2.1 二叉搜索樹的結構2.2 二叉搜索樹的節點尋找2.2.1 非遞歸2.2.2 遞歸 2.3 二叉搜索樹的插入2.3.1 非遞歸2.3.2 遞歸 2.4 二叉搜索樹的刪除2.4.1 非遞歸2.4.2 遞歸 2.5 二叉搜索樹的拷貝 3.二叉樹的應用希望讀者們多多三連支持小…

系統架構設計師:流水線技術相關知識點、記憶卡片、多同類型練習題、答案與解析

流水線記憶要點? ?公式 總時間 (n k - 1)Δt 吞吐率 TP n / 總時間 → 1/Δt&#xff08;max&#xff09; 加速比 S nk / (n k - 1) | 效率 E n / (n k - 1) 關鍵概念 周期&#xff1a;最長段Δt 沖突?&#xff1a; ?數據沖突&#xff08;RAW&#xff09; → 旁路/…

強制重裝及驗證onnxruntime-gpu是否正確工作

#工作記錄 我們經常會遇到明明安裝了onnxruntime-gpu或onnxruntime后&#xff0c;無法正常使用的情況。 一、強制重新安裝 onnxruntime-gpu 及其依賴 # 強制重新安裝 onnxruntime-gpu 及其依賴 pip install --force-reinstall --no-cache-dir onnxruntime-gpu1.18.0 --extra…

桌面我的電腦圖標不見了怎么恢復 恢復方法指南

在Windows操作系統中&#xff0c;“我的電腦”或在較新版本中稱為“此電腦”的圖標&#xff0c;是訪問硬盤驅動器、外部存儲設備和系統文件的重要入口。然而&#xff0c;有些用戶可能會發現桌面上缺少了這個圖標&#xff0c;這可能是由于誤操作、系統設置更改或是不小心刪除造成…

2025.04.20【Lollipop】| Lollipop圖繪制命令簡介

Customize markers See the different options allowing to customize the marker on top of the stem. Customize stems See the different options allowing to customize the stems. 文章目錄 Customize markersCustomize stems Lollipop圖簡介R語言中的Lollipop圖使用ggp…

docker-compose搭建kafka

1、單節點docker-compose.yml version: 3 services:zookeeper:image: zookeeper:3.8container_name: zookeeperports:- "2181:2181"volumes:- ./data/zookeeper:/dataenvironment:ZOO_MY_ID: 1ZOO_MAX_CLIENT_CNXNS: 100kafka:image: bitnami/kafka:3.7container_na…

【問題】一招解決vscode輸出和終端不一致的困擾

背景&#xff08;閑話Trae&#xff09; Trae是挺好&#xff0c;用了幾天&#xff0c;發現它時不時檢查文件&#xff0c;一檢測就轉悠半天&#xff0c;為此我把當前環境清空&#xff0c;就留一個正在調的程序&#xff0c;結果還照樣檢測&#xff0c;雖然沒影響什么&#xff0c;…

Git,本地上傳項目到github

一、Git的安裝和下載 https://git-scm.com/ 進入官網&#xff0c;選擇合適的版本下載 二、Github倉庫創建 點擊右上角New新建一個即可 三、本地項目上傳 1、進入 要上傳的項目目錄&#xff0c;右鍵&#xff0c;選擇Git Bash Here&#xff0c;進入終端Git 2、初始化臨時倉庫…

從零開始配置spark-local模式

1. 環境準備 操作系統&#xff1a;推薦使用 Linux 或 macOS&#xff0c;Windows 也可以&#xff0c;但可能會有一些額外的配置問題。 Java 環境&#xff1a;Spark 需要 Java 環境。確保安裝了 JDK 1.8 或更高版本。 檢查 Java 版本&#xff1a; bash 復制 java -version 如果…

前端~地圖(openlayers)繪制車輛運動軌跡(仿高德)

繪制軌跡路線軌跡路線描邊增加起點終點圖標繪制仿高德方向箭頭模仿車輛動態運動動畫 車輛運行軌跡 車輛軌跡經緯度坐標 const linePoints [new Point([123.676031, 43.653421]),new Point([123.824347, 43.697124]),new Point([124.197882, 43.946811]),new Point([124.104498…

分布式之CAP原則:理解分布式系統的核心設計哲學

聲明&#xff1a;CAP中的P原則都是需要帶著的 在分布式系統的設計與實踐中&#xff0c;CAP原則&#xff08;又稱CAP定理&#xff09;是開發者必須掌握的核心理論之一。它揭示了分布式系統在一致性&#xff08;Consistency&#xff09;、可用性&#xff08;Availability&#x…

IF=40.8|腫瘤免疫:從免疫基因組學到單細胞分析和人工智能

一、寫在前面 今天分享的是發表在《Signal Transduction and Targeted Therapy》上題目為"Technological advances in cancer immunity: from immunogenomics to single-cell analysis and artificial intelligence"的文章。 IF&#xff1a;40.8 DOI:10.1038/s41392…

深入理解 Spring @Bean 注解

在 Spring 框架中,@Bean 注解是用于顯式地聲明一個或多個 Bean 實例,并將其注冊到 Spring 容器中的重要工具。與 @Component 系列注解不同的是,@Bean 是方法級別的注解,通常與 @Configuration 注解結合使用。本文將詳細介紹 @Bean 注解的功能、用法及其應用場景。 1. @Bean…

Pycharm 如何刪除某個 Python Interpreter

在PyCharm中&#xff0c;點擊右下角的“Interpreter Settings”按鈕&#xff0c;或者通過菜單欄選擇“File” > “Settings”&#xff08;macOS用戶選擇“PyCharm” > “Preferences”&#xff09;。在設置窗口中&#xff0c;導航到“Project: [Your Project Name]” >…

如何改電腦網絡ip地址完整教程

更改電腦的網絡IP地址以滿足特定的網絡需求&#xff0c;本文將為您提供一份詳細的步驟指南。其實&#xff0c;改變IP地址并不是一件復雜的事&#xff0c;能解決因為IP限制帶來的麻煩。以下是操作指南&#xff1a; 方法一&#xff1a;Windows 系統&#xff0c;通過圖形界面修改 …

Oracle--SQL性能優化與提升策略

前言&#xff1a;本博客僅作記錄學習使用&#xff0c;部分圖片出自網絡&#xff0c;如有侵犯您的權益&#xff0c;請聯系刪除 一、導致性能問題的內在原因 系統性能問題的底層原因主要有三個方面&#xff1a; CPU占用率過高導致資源爭用和等待內存使用率過高導致內存不足并需…

【go】什么是Go語言中的GC,作用是什么?調優,sync.Pool優化,逃逸分析演示

Go 語言中的 GC 簡介與調優建議 Go語言GC工作原理 對于 Go 而言&#xff0c;Go 的 GC 目前使用的是無分代&#xff08;對象沒有代際之分&#xff09;、不整理&#xff08;回收過程中不對對象進行移動與整理&#xff09;、并發&#xff08;與用戶代碼并發執行&#xff09;的三…

【unity實戰】Animator啟用root motion根運動動畫,實現完美的動畫動作匹配

文章目錄 前言1、動畫分類2、如何使用根位移動畫&#xff1f; 一、根位移動畫的具體使用1、導入人形模型2、導入動畫3、配置動畫參數4、配置角色Animator動畫狀態機5、使用代碼控制人物前進后退 二、問題分析三、Humanoid動畫中的Root Motion機制及相關配置1、Humanoid動畫中的…