多線程初階(2)

說到多線程編程,一定少不了線程安全這個話題。我們前面了解了線程的原理以及線程與進程的關系。線程之間共享資源,這就代表了在多線程編程中一定會產生沖突,所以我們需要在敲代碼時保證線程安全,避免這樣的問題發生。

我們先看一個代碼案例

public class Test10 {static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});t1.start();t2.start();//保證線程都執行完Thread.sleep(1000);System.out.println(count);}
}

在我們看來,運行的結果應該是100000,但是事實并非如此。

運行結果:

1.隨機調度

count++;

這段代碼是看似是一個指令,但實際上是分步執行的。

分為三步的:

1.讀取數據

2.修改數據

3.放回內存

在執行過程中,CPU資源是隨時會被調度走的,也就是說,如果執行到了讀取內存,有可能會被立刻調度走的。這就是所謂的隨機調度。

為了解釋上面的案例可以畫一個時間線:

這也就是造成上面現象的原因之一。

那我們應該如何解決呢?

解決方案1:

利用join方法,在t1執行完后再執行t2,這樣雖然能解決問題,但是失去了并發執行的意義,串行執行的效率也比較低。

public class Test10 {static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});t1.start();t1.join();t2.start();t2.join();//保證線程都執行完Thread.sleep(1000);System.out.println(count);}
}

2.鎖(synchronized)

對于解決上述的線程問題,引入了鎖這一概念。

鎖是什么,我們可以舉個具體的例子來了解一下:

我們可以把鎖看成家里的鎖,當家里沒人時,門是從外面鎖上的,這時擁有鑰匙的人就可以打開鎖并進去執行任務,但為了在執行任務的時候是安全的,所以會從里面鎖上,這時外面的人就算有鑰匙也打不開門,當里面的人執行完任務的出來后還會把門帶上,這時外面擁有鑰匙的人就可以開門進去了。這其實就是鎖是如何解決線程安全問題的類似原理。

public class Test10 {static int count = 0;public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (locker) {count++;}}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (locker) {count++;}}});t1.start();t2.start();//        t1.start();
//        t1.join();
//        t2.start();
//        t2.join();//保證線程都執行完Thread.sleep(1000);System.out.println(count);}
}

運行結果:

我們先了解一下使用鎖的格式:

synchronized(鎖對象){}

這里的鎖對象可以是任意引用類型的對象,但要保證在解決同一個非原子問題時是同一個對象。

synchronized的使用方法除了上述格式以外,還能用來修飾方法:

這里的鎖對象是this。

class Counter{int count = 0;public synchronized void addCount(){count++;}
}public class Test11 {public static void main(String[] args) throws InterruptedException {Object locker = new Object();Counter counter = new Counter();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {counter.addCount();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {counter.addCount();}});t1.start();t2.start();Thread.sleep(100);System.out.println(counter.count);}
}

運行結果:

其實這樣的解決方法也就是將局部的任務給串行化,只不過比直接將整個線程串行化來的含蓄,性能降低的少。

可重入

在Java中synchronized具有可重入的特點。

synchronized (locker) {synchronized (locker) {count++;}}

我們知道,鎖是具有互斥性的,也就是在上鎖后是需要解鎖后才能讓下一個擁有鎖對象的任務執行,那上面這段代碼就會形成一個死鎖的現象,當進入第一個鎖后,會遇到第二個鎖,但是想要進入第二個synchronized是需要從第一個synchronized中出來的,但是要想從第一個synchronized中出來就需要進入第二個synchronized,所以這就形成了一個死循環,可以叫死鎖。

Java的開發人員為了解決這一問題就賦予了synchronized可重入這一屬性,也就是在上述情況下不會出現死鎖的現象,Java會自動識別出來。

如果有很多層synchronized嵌套的話,當第一次進入的synchronized結束時,這把鎖才會解開。

死鎖?

上面講可重入性的時候講到了死鎖這一概念,這里就詳細講講死鎖。

造成死鎖有三種情況:

1.一個線程同一個鎖加多次,這也是講述可重入性時舉的例子。

2.N個線程,M把鎖。

public class Test12 {public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(()->{synchronized (locker1){try {System.out.println("t1獲取了locker1!");//確保t2先拿到locker2Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){System.out.println("t1獲取了兩把鎖!");}}});Thread t2 = new Thread(()->{synchronized (locker2){try {System.out.println("t2獲取了locker2!");//確保線程t1先拿到locker1Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker1){System.out.println("t2獲取了兩把鎖!");}}});t1.start();t2.start();}
}

運行結果:

進程并沒有結束,使用jconsole查看線程狀態,發現是BLOCKED,也就鎖造成的無上限的阻塞等待。這是因為在線程t1拿到locker1和t2拿到locker2的情況下,t1要想拿到locker2就必須要讓t2解鎖,而t2要想解鎖,就需要拿到locker1,但是locker1在t1手中,所以就形成了死循環,也就構成了死鎖。

3.哲學家就餐問題

假設有七個哲學家圍著一個桌子吃飯,每兩個人中間放一根筷子,這樣的話當一個人用筷子吃飯的時候,他兩側的人都沒法用餐。

在一種極端情況下,每個哲學家都拿起他左手邊的筷子,這樣所有哲學家都在等待對方放下筷子,就形成了死鎖。

public class Test13 {public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Object locker3 = new Object();Object locker4 = new Object();Object locker5 = new Object();Thread t1 = new Thread(()->{synchronized (locker1){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker5){}}});Thread t2 = new Thread(()->{synchronized (locker2){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker1){}}});Thread t3 = new Thread(()->{synchronized (locker3){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){}}});Thread t4 = new Thread(()->{synchronized (locker4){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker3){}}});Thread t5 = new Thread(()->{synchronized (locker5){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker4){}}});t1.start();t2.start();t3.start();t4.start();t5.start();}
}

各線程狀態:?

解決方案:

1.對筷子按順序進行編號,先拿到左右小的編號的筷子,拿到后再拿左右大的編號的筷子。

剛開始1號先吃到飯,吃完后放下1號和7號筷子,2號拿到1號筷子,7號拿到7號筷子,7號可以就餐,以此類推,所有人都可以完成就餐。

public class Test13 {public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Object locker3 = new Object();Object locker4 = new Object();Object locker5 = new Object();Thread t1 = new Thread(()->{synchronized (locker1){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker5){}}});Thread t2 = new Thread(()->{//保證t1先拿到locker1,如果t2先拿到locker1,還是會形成死鎖try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker1){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){}}});Thread t3 = new Thread(()->{synchronized (locker2){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker3){}}});Thread t4 = new Thread(()->{synchronized (locker3){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker4){}}});Thread t5 = new Thread(()->{synchronized (locker4){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker5){}}});t1.start();t2.start();t3.start();t4.start();t5.start();}
}

運行結果:

?4.死鎖總結:

造成死鎖的原因:

1.鎖具有互斥性(鎖的基本特性)

當一個鎖被一個線程獲取之后,當別的線程想要獲取這個鎖的時候,會線程阻塞。

2.鎖不可搶占(鎖的基本特性)

當這個鎖已經被獲取時,別的線程是不能強行搶占這個鎖的,?必須等待獲取。

3.請求和保持

當一個線程已經有至少一個鎖的時候,嘗試獲取別的鎖遇到阻塞,這時候該線程也不會放棄原來的鎖。

4.循環等待

線程1等待線程2,線程2等待線程3,線程3等待線程4,線程4等待線程5,線程5等待線程1,這樣就產生了死循環。

解決方案:

1.把嵌套的鎖改為并列的鎖。(基于N個線程,M把鎖的代碼例子)

public class Test14 {public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(()->{synchronized (locker1){System.out.println("t1拿到locker1!");
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    throw new RuntimeException(e);
//                }}synchronized (locker2){System.out.println("t1拿到locker2!");}});Thread t2 = new Thread(()->{synchronized (locker2){System.out.println("t2拿到locker2!");
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    throw new RuntimeException(e);
//                }}synchronized (locker1){System.out.println("t2拿到locker1!");}});t1.start();t2.start();}
}

運行結果:

2.規定加鎖順序編號遞增/遞減(基于哲學家就餐問題)

代碼案例在上述哲學家就餐問題中的Test13.

3.java標準庫中的線程安全類:

StringBuffer,Hashtable,Vector,ConcurrentHashMap,String。。。。。。

前三個不推薦使用。

不安全類:
StringBuilder,ArrayList,LinkedList,HashMap,TreeMap,HashSet,TreeSet。

以StringBuffer為例,查看標準庫中的代碼,發現其內部是由簡單加synchronized實現的,當面對比較復雜的情況時,很有可能會出現bug~~

4.wait/notify

在有些情況下我們需要讓某個線程處于阻塞狀態,在完成某些任務后再進行喚醒。

注意在調用wait/notify時必須實在synchronized的代碼塊中,并且必須是相同的鎖對象才行。

wait下的線程狀態:WAITING

public class Test16 {public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(()->{synchronized (locker) {System.out.println("線程t1wait......");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("線程t1被喚醒!");}});Thread t2 = new Thread(()->{synchronized (locker) {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t2線程嘗試喚醒t1......");locker.notify();}});t1.start();t2.start();}
}

運行結果:

在線程wait期間,該線程是主動放棄了CPU資源的,是解鎖狀態,暫時不會參與鎖競爭。

這種情況下,當使用notify喚醒時只能喚醒其中一個,并且是隨機的,這就有很大的不確定性在里面,所以java標準庫中還提供了notifyAll方法,能夠喚醒所有相同鎖對象的wait。

對于notify是隨即喚醒這一點還有可能會造成線程餓死,所謂線程餓死也就是某個線程長時間沒有吃到CPU的資源。

public class Test17 {public static void main(String[] args) {Object l1 = new Object();Thread t1 = new Thread(()->{synchronized (l1){try {l1.wait();System.out.println("t1被喚醒!");} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t2 = new Thread(()->{synchronized (l1){try {l1.wait();System.out.println("t2被喚醒!");}    catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t3 = new Thread(()->{synchronized (l1){try {l1.wait();System.out.println("t3被喚醒!");} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t4 = new Thread(()->{System.out.println("輸入任意內容喚醒所有線程:");Scanner sc = new Scanner(System.in);sc.next();synchronized (l1){l1.notifyAll();}});t1.start();t2.start();t3.start();t4.start();}
}

運行結果:

?

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

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

相關文章

【Ubuntu】安裝向日葵遠程控制

前言 在Ubuntu 24.04.2下安裝向日葵遠程控制出錯&#xff0c;少了一些依賴&#xff0c;需要安裝一些依賴。 1.安裝gconf2-common wget http://mirrors.kernel.org/ubuntu/pool/universe/g/gconf/gconf2-common_3.2.6-6ubuntu1_all.deb sudo dpkg -i gconf2-common_3.2.6-6ub…

【Python開源】深度解析:一款高效音頻封面批量刪除工具的設計與實現

&#x1f3b5; 【Python開源】深度解析&#xff1a;一款高效音頻封面批量刪除工具的設計與實現 &#x1f308; 個人主頁&#xff1a;創客白澤 - CSDN博客 &#x1f525; 系列專欄&#xff1a;&#x1f40d;《Python開源項目實戰》 &#x1f4a1; 熱愛不止于代碼&#xff0c;熱情…

JAVA房屋租售管理系統房屋出租出售平臺房屋銷售房屋租賃房屋交易信息管理源碼

一、源碼描述 這是一套房屋租售管理源碼&#xff0c;基于SpringBootVue框架&#xff0c;后端采用JAVA開發&#xff0c;源碼功能完善&#xff0c;涵蓋了房屋租賃、房屋銷售、房屋交易等業務。 二、源碼截圖

一篇文章講清楚mysql的聚簇索引、非聚簇索引、輔助索引

聚簇索引與非聚簇索引最大的區別就是&#xff1a; 聚簇索引的索引和數據是存放在一起的&#xff0c;都是在葉子結點&#xff1b; 非聚簇索引的索引和數據是分開存儲的&#xff0c;葉子節點存放的是索引和指向數據文件的地址&#xff0c;通過葉子節點找到索引&#xff0c;再通…

使用ESPHome燒錄固件到ESP32-C3并接入HomeAssistant

文章目錄 一、安裝ESPHome二、配置ESP32-C3控制燈1.主配置文件esp32c3-luat.yaml2.基礎通用配置base.yaml3.密碼文件secret.yaml4.圍欄燈four_light.yaml5.彩燈rgb_light.yaml6.左右柱燈left_right_light.yaml 三、安裝固件四、HomeAssistant配置ESPHome1.直接訪問2.配置ESPHom…

什么是變量提升?

變量提升&#xff08;Hoisting&#xff09; 是 JavaScript 引擎在代碼執行前的一個特殊行為&#xff0c;它會將變量聲明和函數聲明自動移動到當前作用域的頂部。但需要注意的是&#xff0c;只有聲明會被提升&#xff0c;賦值操作不會提升。 ??核心概念?? 變量聲明提升&…

【萬字長文】深入淺出 LlamaIndex 和 LangChain:從RAG到智能體,輕松駕馭LLM應用開發

Langchain系列文章目錄 01-玩轉LangChain&#xff1a;從模型調用到Prompt模板與輸出解析的完整指南 02-玩轉 LangChain Memory 模塊&#xff1a;四種記憶類型詳解及應用場景全覆蓋 03-全面掌握 LangChain&#xff1a;從核心鏈條構建到動態任務分配的實戰指南 04-玩轉 LangChai…

2025 后端自學UNIAPP【項目實戰:旅游項目】3、API接口請求封裝,封裝后的簡單測試以及實際使用

一、創建請求封裝目錄 選中自己的項目&#xff0c;右鍵鼠標---->新建---->目錄---->名字自定義【我的是api】 二、創建兩個js封裝文件 選中封裝的目錄&#xff0c;右鍵鼠標---->新建---->js文件---->名字自定義【我的兩個js文件分別是my_http和my_api】 三…

autojs和冰狐智能輔助該怎么選擇?

最近打算做自動化腳本&#xff0c;在autojs和冰狐智能輔助中做選擇&#xff0c;不知道該怎么選。沒辦法只能花費大量時間仔細研究了autojs和冰狐智能輔助&#xff0c;綜合考慮功能需求、開發復雜度、編程經驗及項目規模等因素。以下是兩者的核心對比及選擇建議&#xff0c;僅供…

python24-匿名函數

課程&#xff1a;B站大學 記錄python學習&#xff0c;直到學會基本的爬蟲&#xff0c;使用python搭建接口自動化測試就算學會了&#xff0c;在進階webui自動化&#xff0c;app自動化 匿名函數 匿名函數實踐是檢驗真理的唯一標準 匿名函數 匿名函數是指沒有名字的函數&#xff…

Android 查看 Logcat (可純手機方式 無需電腦)

安裝 Logcat Reader Github Google Play 如果有電腦 使用其ADB方式可執行如下命令 后續無需安裝Termux # 使用 ADB 授予 android.permission.READ_LOGS 權限給 Logcat Reader adb shell "pm grant com.dp.logcatapp android.permission.READ_LOGS && am force-…

驅動開發硬核特訓 · Day 30(上篇):深入理解 I2C 總線驅動模型(以 at24 EEPROM 為例)

作者&#xff1a;嵌入式Jerry 視頻教程請關注 B 站&#xff1a;“嵌入式Jerry” 一、寫在前面 在上一階段我們已經深入理解了字符設備驅動與設備模型之間的結合方式、sysfs 的創建方式以及平臺驅動模型的實際運用。今天我們邁入總線驅動模型的世界&#xff0c;聚焦于 I2C 總線…

超詳細講解注意力機制、自注意力機制、多頭注意力機制、通道注意力機制、空間注意力機制

在如今的機器學習和深度學習領域&#xff0c;注意力機制絕對是一個熱度居高不下的話題。事實上&#xff0c;注意力機制并不是一個全新的概念&#xff0c;早在多年前就已經被提出并應用。比如在圖像分類任務中&#xff0c;SENet 和 ECA-Net 等模型中都運用了注意力機制&#xff…

Wireshark基本使用

本文會對Wireshark做簡單介紹&#xff0c;帶大家熟悉一下Wireshark的界面&#xff0c;以及如何使用過濾器。 接著會帶大家查看TCP五層模型下&#xff0c;帶大家回顧各層首部的格式。 最后會演示 Wireshark 如何抓取三次握手和四次揮手包的過程。 目錄 一.Wireshark簡介 二…

加速項目落地(Trae編輯器)

目錄 vscode安裝python支持 vscode常用插件 Trae編輯器 兩個界面合成 補充&#xff08;QT開發的繁瑣&#xff09; AI編程哪家強&#xff1f;Cursor、Trae深度對比&#xff0c;超詳細&#xff01; - 知乎 Trae兼容vscode的插件&#xff0c;我們可以先在vscode里面裝好再一…

stable diffusion的attention-map:提取和可視化跨注意力圖

項目&#xff1a; wooyeolbaek/attention-map-diffusers: &#x1f680; Cross attention map tools for huggingface/diffusers 參考&#xff1a;【可視化必備技能&#xff08;1&#xff09;】SD / Flux 文生圖模型的 Attention Map 可視化-CSDN博客

多環串級PID

文章目錄 為什么要多環程序主函數內環外環 雙環PID調參內環Kp調法Ki調法 外環Kp 以一定速度到達指定位置封裝 為什么要多環 單環只能單一控制速度或者位置&#xff0c;如果想要同時控制多個量如速度&#xff0c;位置&#xff0c;角度&#xff0c;就需要多個PID 速度環一般PI…

基于Kubernetes的Apache Pulsar云原生架構解析與集群部署指南(上)

#作者&#xff1a;閆乾苓 文章目錄 概念和架構概述主要特點消息傳遞核心概念Pulsar 的消息模型Pulsar 的消息存儲與分發Pulsar 的高級特性架構BrokerBookKeeperZooKeeper 概念和架構 概述 Pulsar 是一個多租戶、高性能的服務器到服務器消息傳遞解決方案。Pulsar 最初由雅虎開…

電子電氣架構 --- 如何有助于提安全性并減少事故

我是穿拖鞋的漢子,魔都中堅持長期主義的汽車電子工程師。 老規矩,分享一段喜歡的文字,避免自己成為高知識低文化的工程師: 鈍感力的“鈍”,不是木訥、遲鈍,而是直面困境的韌勁和耐力,是面對外界噪音的通透淡然。 生活中有兩種人,一種人格外在意別人的眼光;另一種人無論…

rest_framework學習之認證 權限

權限 DRF提供如下幾種常見權限&#xff1a; IsAuthenticated, 認證通過 IsAdminUser, 管理員權限 IsAuthenticatedOrReadOnly, 登錄用戶增刪改 非登錄用戶只能查詢 AllowAny&#xff0c;無需認證&#xff08;默認&#xff09; 在rest_framework的APIView基礎類中&#xf…