javaSE學習筆記21-線程(thread)-鎖(synchronized 與Lock)

死鎖

多個線程各自占有一些共享資源,并且互相等待其他線程占有的資源才能運行,而導致兩個或者多個線程 都在等待對方釋放資源,都停止執行的情形,某一個同步塊同時擁有“兩個以上對象的鎖”時,就可能 會發生“死鎖"的問題;

死鎖是指多個線程在執行過程中,因為爭奪資源而造成的一種互相等待的現象,導致這些線程都無法繼續執行下去。

練習代碼

package com.lock;/*
死鎖
多個線程各自占有一些共享資源,并且互相等待其他線程占有的資源才能運行,而導致兩個或者多個線程
都在等待對方釋放資源,都停止執行的情形,某一個同步塊同時擁有“兩個以上對象的鎖”時,就可能
會發生“死鎖"的問題;*///死鎖:多個線程互相抱著對方需要的資源,然后形成僵持
public class DeadLock {public static void main(String[] args) {Makeup g1 = new Makeup(0,"灰姑涼");Makeup g2 = new Makeup(0,"白雪公主");g1.start();g2.start();}
}//口紅(Lipstick)
class Lipstick{}//鏡子(Mirror)
class Mirror{}//化妝(Makeup)
class Makeup extends Thread{//需要的資源只有一份,用static來抱著只有一份static Lipstick lipstick = new Lipstick();static Mirror mirror = new Mirror();int choice;//選擇String girlName;//使用化妝品的人Makeup(int choice,String girlName){this.choice = choice;this.girlName = girlName;}@Overridepublic void run() {//化妝try {makeup();} catch (InterruptedException e) {e.printStackTrace();}}//化妝,互相持有對方的鎖,就是需要拿到對方的資源private void makeup() throws InterruptedException {if (choice == 0) {synchronized (lipstick) {//獲得口紅的鎖System.out.println(this.girlName + "獲得口紅的鎖");Thread.sleep(1000);synchronized (mirror){//1s鐘后想獲得鏡子 的鎖System.out.println(this.girlName + "獲得鏡子的鎖");}}}else {synchronized (mirror) {//獲得鏡子的鎖System.out.println(this.girlName + "獲得鏡子的鎖");Thread.sleep(2000);synchronized (lipstick){//2s鐘后想獲得口紅 的鎖System.out.println(this.girlName + "獲得口紅的鎖");}}}}
}

代碼結構

  1. DeadLock類:這是主類,包含main方法,用于啟動兩個線程。

  2. Lipstick類和Mirror類:這兩個類分別代表口紅和鏡子,是共享資源。

  3. Makeup類:繼承自Thread類,表示一個化妝的線程。每個線程代表一個女孩,她們需要使用口紅和鏡子來化妝。

代碼邏輯

  1. 共享資源

    • LipstickMirror是兩個共享資源,分別代表口紅和鏡子。

    • 這兩個資源被聲明為static,確保它們在所有Makeup實例之間共享。

  2. Makeup類

    • choice:表示女孩的選擇,決定她們先獲取哪個資源。

    • girlName:表示女孩的名字。

    • run()方法:線程啟動后執行的方法,調用makeup()方法。

    • makeup()方法:模擬化妝過程,嘗試獲取口紅和鏡子的鎖。

  3. 死鎖的產生

    • 如果choice為0,線程會先獲取口紅的鎖,然后嘗試獲取鏡子的鎖。

    • 如果choice為1,線程會先獲取鏡子的鎖,然后嘗試獲取口紅的鎖。

    • 由于兩個線程的執行順序不同,可能會導致以下情況:

      • 線程1(灰姑涼)持有口紅的鎖,等待鏡子的鎖。

      • 線程2(白雪公主)持有鏡子的鎖,等待口紅的鎖。

    • 這樣,兩個線程互相等待對方釋放資源,導致死鎖。

代碼執行流程

  1. 啟動線程

    • g1g2兩個線程分別啟動,代表灰姑涼和白雪公主。

    • g1choice為0,g2choice為1。

  2. 線程執行

    • g1先獲取口紅的鎖,然后嘗試獲取鏡子的鎖。

    • g2先獲取鏡子的鎖,然后嘗試獲取口紅的鎖。

  3. 死鎖發生

    • g1持有口紅的鎖,等待g2釋放鏡子的鎖。

    • g2持有鏡子的鎖,等待g1釋放口紅的鎖。

    • 兩個線程都無法繼續執行,形成死鎖。

如何避免死鎖

  1. 鎖的順序:確保所有線程以相同的順序獲取鎖。例如,所有線程都先獲取口紅的鎖,再獲取鏡子的鎖。

  2. 超時機制:在獲取鎖時設置超時時間,如果超時則釋放已持有的鎖并重試。

  3. 死鎖檢測:使用工具或算法檢測死鎖,并采取相應措施解除死鎖。

優化后代碼

package com.lock;/*
死鎖
多個線程各自占有一些共享資源,并且互相等待其他線程占有的資源才能運行,而導致兩個或者多個線程
都在等待對方釋放資源,都停止執行的情形,某一個同步塊同時擁有“兩個以上對象的鎖”時,就可能
會發生“死鎖"的問題;死鎖避免方法
產生死鎖的四個必要條件
1.互斥條件:一個資源每次只能被一個進程使用;
2.請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放;
3.不剝奪條件:進程已獲得的資源,在未使用完之前,不能強行剝奪;
4,循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。只要想辦法破其中的任意一個或多個條件就可以避免死鎖發生*///死鎖:多個線程互相抱著對方需要的資源,然后形成僵持
public class DeadLock {public static void main(String[] args) {Makeup g1 = new Makeup(0,"灰姑涼");Makeup g2 = new Makeup(0,"白雪公主");g1.start();g2.start();}
}//口紅(Lipstick)
class Lipstick{}//鏡子(Mirror)
class Mirror{}//化妝(Makeup)
class Makeup extends Thread{//需要的資源只有一份,用static來抱著只有一份static Lipstick lipstick = new Lipstick();static Mirror mirror = new Mirror();int choice;//選擇String girlName;//使用化妝品的人Makeup(int choice,String girlName){this.choice = choice;this.girlName = girlName;}@Overridepublic void run() {//化妝try {makeup();} catch (InterruptedException e) {e.printStackTrace();}}//化妝,互相持有對方的鎖,就是需要拿到對方的資源private void makeup() throws InterruptedException {if (choice == 0) {synchronized (lipstick) {//獲得口紅的鎖System.out.println(this.girlName + "獲得口紅的鎖");Thread.sleep(1000);}synchronized (mirror){//1s鐘后想獲得鏡子 的鎖System.out.println(this.girlName + "獲得鏡子的鎖");}}else {synchronized (mirror) {//獲得鏡子的鎖System.out.println(this.girlName + "獲得鏡子的鎖");Thread.sleep(2000);}synchronized (lipstick){//2s鐘后想獲得口紅 的鎖System.out.println(this.girlName + "獲得口紅的鎖");}}}
}

修改后的代碼分析

關鍵修改點
  1. 鎖的嵌套被移除

    • 在原始代碼中,synchronized塊是嵌套的,即一個線程在持有第一個鎖的情況下嘗試獲取第二個鎖。

    • 在修改后的代碼中,synchronized塊是分開的,線程在釋放第一個鎖之后才會嘗試獲取第二個鎖。

  2. 鎖的獲取順序

    • 修改后的代碼中,線程不會同時持有兩個鎖,而是先釋放一個鎖,再嘗試獲取另一個鎖。

    • 這樣就不會出現兩個線程互相等待對方釋放鎖的情況。


修改后的代碼執行邏輯

線程1(灰姑涼)的執行流程:
  1. 獲取lipstick的鎖。

  2. 打印“灰姑涼獲得口紅的鎖”。

  3. 釋放lipstick的鎖。

  4. 獲取mirror的鎖。

  5. 打印“灰姑涼獲得鏡子的鎖”。

  6. 釋放mirror的鎖。

線程2(白雪公主)的執行流程:
  1. 獲取mirror的鎖。

  2. 打印“白雪公主獲得鏡子的鎖”。

  3. 釋放mirror的鎖。

  4. 獲取lipstick的鎖。

  5. 打印“白雪公主獲得口紅的鎖”。

  6. 釋放lipstick的鎖。


為什么避免了死鎖?

  1. 鎖的釋放

    • 每個線程在獲取一個鎖后,會先釋放它,再嘗試獲取另一個鎖。

    • 這樣就不會出現一個線程持有lipstick的鎖并等待mirror的鎖,而另一個線程持有mirror的鎖并等待lipstick的鎖的情況。

  2. 沒有互相等待

    • 線程1和線程2不會同時持有對方需要的鎖,因此不會形成互相等待的僵局。

Lock鎖?

1、JDK5.0開始,Java提供了更強大的線程同步機制--通過顯式定義同步鎖對象來實現同步。
同步鎖使用Lock對象充當
2、java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進行訪問的工具。
鎖提供了對共享資源的獨占訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共享資源之前應先獲得Lock對象
ReentrantLoc類(可重入鎖)實現了Lock,它擁有與synchronized相同的并發性和內存語義,在實現線程安全的控制中,比較常用的是
ReentrantLock,可以顯式加鎖、釋放鎖

以下代碼演示了如何使用ReentrantLock來實現線程同步,確保多個線程安全地訪問共享資源。ReentrantLock是Java中提供的一種顯式鎖機制,相比于synchronized關鍵字,它提供了更靈活的鎖控制方式。

package com.lock;import java.util.concurrent.locks.ReentrantLock;/*
Lock(鎖)
1、JDK5.0開始,Java提供了更強大的線程同步機制--通過顯式定義同步鎖對象來實現同步。
同步鎖使用Lock對象充當
2、java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進行訪問的工具。
鎖提供了對共享資源的獨占訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共享資源之前應先獲得Lock對象
ReentrantLoc類(可重入鎖)實現了Lock,它擁有與synchronized相同的并發性和內存語義,在實現線程安全的控制中,比較常用的是
ReentrantLock,可以顯式加鎖、釋放鎖*/
//測試lock鎖
public class TestLock {public static void main(String[] args) {TestLock2 testLock2 = new TestLock2();new Thread(testLock2).start();new Thread(testLock2).start();new Thread(testLock2).start();}
}class TestLock2 implements Runnable{int ticketNums = 10;//定義lock鎖private final ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true){try {lock.lock();//加鎖if (ticketNums > 0) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(ticketNums--);}else {break;}}finally {//解鎖lock.unlock();}}}
}

代碼結構

  1. TestLock類

    • 這是主類,包含main方法,用于啟動多個線程。

    • 創建了一個TestLock2對象,并啟動三個線程來執行該對象的run方法。

  2. TestLock2類

    • 實現了Runnable接口,表示一個任務,可以被多個線程執行。

    • 包含一個共享資源ticketNums(票數),多個線程會競爭訪問和修改這個資源。

    • 使用ReentrantLock來確保對ticketNums的訪問是線程安全的。


代碼邏輯

1.?共享資源
  • ticketNums:表示剩余的票數,初始值為10。

  • 多個線程會同時訪問和修改ticketNums,因此需要確保線程安全。

2.?ReentrantLock
  • ReentrantLock是一個可重入鎖,允許線程多次獲取同一把鎖。

  • 通過lock()方法加鎖,通過unlock()方法解鎖。

  • 使用try-finally塊確保鎖一定會被釋放,避免死鎖。

3.?線程執行邏輯
  • 每個線程執行TestLock2run方法。

  • while (true)循環中,線程不斷嘗試獲取鎖并訪問共享資源ticketNums

  • 如果ticketNums > 0,線程會休眠1秒(模擬耗時操作),然后打印并減少ticketNums的值。

  • 如果ticketNums <= 0,線程退出循環,任務結束。


代碼執行流程

  1. 啟動線程

    • main方法中,創建了一個TestLock2對象,并啟動三個線程。

    • 這三個線程會并發執行TestLock2run方法。

  2. 線程競爭鎖

    • 每個線程在執行run方法時,會先調用lock.lock()嘗試獲取鎖。

    • 只有一個線程能成功獲取鎖,其他線程會被阻塞,直到鎖被釋放。

  3. 訪問共享資源

    • 獲取鎖的線程會檢查ticketNums的值。

    • 如果ticketNums > 0,線程會休眠1秒,然后打印ticketNums的值并將其減1。

    • 如果ticketNums <= 0,線程會退出循環。

  4. 釋放鎖

    • 線程在完成對共享資源的操作后,會調用lock.unlock()釋放鎖。

    • 其他被阻塞的線程會競爭獲取鎖,繼續執行。

  5. 任務結束

    • ticketNums的值減少到0時,所有線程都會退出循環,任務結束。

關鍵點

  1. ReentrantLock的作用

    • 確保多個線程對共享資源ticketNums的訪問是互斥的,避免數據競爭。

    • 相比于synchronizedReentrantLock提供了更靈活的鎖控制,例如可中斷鎖、超時鎖等。

  2. try-finally的作用

    • try塊中加鎖,在finally塊中解鎖,確保鎖一定會被釋放,避免死鎖。

  3. 線程安全

    • 通過ReentrantLock實現了對共享資源的線程安全訪問。


改進建議

  1. 鎖的粒度

    • 當前代碼中,鎖的粒度較大(整個while循環都在鎖內),可能會影響并發性能。可以根據實際需求調整鎖的粒度。

  2. 公平鎖

    • ReentrantLock默認是非公平鎖,可以通過構造函數new ReentrantLock(true)創建公平鎖,確保線程按順序獲取鎖。

  3. 鎖的可中斷性

    • ReentrantLock支持可中斷的鎖獲取(lockInterruptibly()),可以在線程等待鎖時響應中斷。

改進后代碼:

package com.lock;import java.util.concurrent.locks.ReentrantLock;/*
改進后的TestLock示例:
1. 縮小鎖的粒度,只對共享資源的訪問和修改加鎖。
2. 使用公平鎖,確保線程按順序獲取鎖。
3. 優化代碼結構,提高可讀性和可維護性。
*/public class TestLock {public static void main(String[] args) {TestLock2 testLock2 = new TestLock2();// 啟動多個線程new Thread(testLock2, "線程1").start();new Thread(testLock2, "線程2").start();new Thread(testLock2, "線程3").start();}
}class TestLock2 implements Runnable {private int ticketNums = 10; // 共享資源,表示剩余的票數// 定義公平鎖private final ReentrantLock lock = new ReentrantLock(true);@Overridepublic void run() {while (true) {// 嘗試獲取鎖lock.lock();try {if (ticketNums > 0) {// 模擬耗時操作try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 打印當前線程名和剩余的票數System.out.println(Thread.currentThread().getName() + " 售出票號:" + ticketNums--);} else {// 票已售完,退出循環break;}} finally {// 釋放鎖lock.unlock();}// 模擬線程切換,增加并發性try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}
}

synchronized 與Lock的對比

1、lock是顯式鎖(手動開啟和關閉鎖,別忘記關閉鎖)synchronized是隱式鎖,出了作用域自動釋放
2、Lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖;
3、使用Lock鎖,JVM將花費較少的時間來調度線程(性能更好,并且具有更好的擴展性(提供更多的子類))
4、優先使用順序:
??? Lock > 同步代碼塊(已經進入了方法體,分配了相應資源)> 同步方法(在方法體之外)

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

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

相關文章

uni-app發起網絡請求的三種方式

uni.request(OBJECT) 發起網絡請求 具體參數可查看官方文檔uni-app data:請求的參數; header&#xff1a;設置請求的 header&#xff0c;header 中不能設置 Referer&#xff1b; method&#xff1a;請求方法&#xff1b; timeout&#xff1a;超時時間&#xff0c;單位 ms&a…

SpringBoot速成概括

視頻&#xff1a;黑馬程序員SpringBoot3Vue3全套視頻教程&#xff0c;springbootvue企業級全棧開發從基礎、實戰到面試一套通關_嗶哩嗶哩_bilibili 圖示&#xff1a;

GoFound 與 MySQL 集成優化方案

GoFound 與 MySQL 集成優化方案 1. 明確需求 文章信息存儲在 MySQL 數據庫中。使用 GoFound 實現全文搜索功能。搜索時&#xff0c;先從 GoFound 中獲取匹配的文章 ID&#xff0c;然后從 MySQL 中查詢完整的文章信息。 2. 優化思路 數據同步&#xff1a;將 MySQL 中的文章數…

基于開源Odoo模塊、SKF Phoenix API與IMAX-8數采網關的資產密集型企業設備智慧運維實施方案

一、方案背景與需求分析 1.1 華東地區產業特點與設備管理痛點 華東地區作為中國制造業核心區域&#xff0c;聚集了鋼鐵、化工、汽車、裝備制造等資產密集型企業。以某長三角鋼鐵集團為例&#xff0c;其設備管理面臨以下挑戰&#xff1a; 非計劃停機損失嚴重&#xff1a;2023…

《魔女的夜宴》無廣版手游安卓蘋果免費下載直裝版

自娶 https://pan.xunlei.com/s/VOJS77k8NDrVawqcOerQln2lA1?pwdn6k8 《魔女的夜宴》&#xff1a;一場魔法與戀愛的奇幻之旅 在美少女游戲的世界中&#xff0c;柚子社&#xff08;Yuzusoft&#xff09;的作品總是以其精美的畫面、動人的劇情和豐富的角色塑造而備受玩家喜愛…

深化與細化:提示工程(Prompt Engineering)的進階策略與實踐指南2

深化與細化&#xff1a;提示工程&#xff08;Prompt Engineering&#xff09;的進階策略與實踐指南 一、結構化提示的黃金框架 1. CRISPE框架&#xff08;角色-約束-意圖-風格-示例&#xff09; 適用于復雜技術場景&#xff0c;確保輸出精準可控&#xff1a; [角色] 你是一名…

N-bit ADC過采樣和L階噪聲整形后的SQNR表達式

對于采用L階理想高通濾波器進行噪聲整形的DSM&#xff0c;OSR每增加一倍&#xff0c;SQNR提高3(2L1)dB,文中給出了DSM量化精度與量化器位數N、環路濾波器階數L和過采樣率OSR的關系&#xff0c;在進行DSM系統設計時通過設置目標SQNR即可篩選出滿足設計需要的參數組合。

Linux環境開發工具

Linux軟件包管理器yum Linux下安裝軟件方式&#xff1a; 源代碼安裝rpm安裝——Linux安裝包yum安裝——解決安裝源、安裝版本、安裝依賴的問題 yum對應于Windows系統下的應用商店 使用Linux系統的人&#xff1a;大部分是職業程序員 客戶端怎么知道去哪里下載軟件&#xff1…

自動化辦公|通過xlwings進行excel格式設置

1. 介紹 xlwings 是一個強大的 Python 庫&#xff0c;可以用來操作 Excel&#xff0c;包括設置單元格格式、調整行高列寬、應用條件格式以及使用內置樣式。本文將詳細介紹如何使用 xlwings 進行 Excel 格式化操作&#xff0c;并附帶代碼示例。 2. 基礎格式設置&#xff08;字…

EasyRTC:智能硬件適配,實現多端音視頻互動新突破

一、智能硬件全面支持&#xff0c;輕松跨越平臺障礙 EasyRTC 采用前沿的智能硬件適配技術&#xff0c;無縫對接 Windows、macOS、Linux、Android、iOS 等主流操作系統&#xff0c;并全面擁抱 WebRTC 標準。這一特性確保了“一次開發&#xff0c;多端運行”的便捷性&#xff0c…

【架構思維基礎:如何科學定義問題】

架構思維基礎&#xff1a;如何科學定義問題 一、問題本質認知 1.1 問題矛盾 根據毛澤東《矛盾論》&#xff0c;問題本質是系統內部要素間既對立又統一的關系。例如&#xff1a; 電商系統矛盾演變&#xff1a; 90年代&#xff1a;商品供給不足 vs 消費需求增長00年代&#x…

從零開始構建一個小型字符級語言模型的詳細教程(基于Transformer架構)之一數據準備

最近特別火的DeepSeek,是一個大語言模型,那一個模型是如何構建起來的呢?DeepSeek基于Transformer架構,接下來我們也從零開始構建一個基于Transformer架構的小型語言模型,并說明構建的詳細步驟及內部組件說明。我們以構建一個字符級語言模型(Char-Level LM)為例,目標是通…

Effective Go-新手學習Go需要了解的知識

不知不覺從事Golang開發已有4+年了,回顧自己的成長經歷,有很多感悟和心得。如果有人問我,學習Golang從什么資料開始,我一定給他推薦"Effective Go"。《Effective Go》是 Go 語言官方推薦的編程風格和最佳實踐指南,其結構清晰,內容涵蓋 Go 的核心設計哲學和常見…

坐井說天闊---DeepSeek-R1

前言 DeepSeek-R1這么火&#xff0c;雖然網上很多介紹和解讀&#xff0c;但聽人家的總不如自己去看看原論文。于是花了大概一周的時間&#xff0c;下班后有進入了研究生的狀態---讀論文。 DeepSeek這次的目標是探索在沒有任何監督數據的情況下訓練具有推理能力的大模型&#…

MySQL(1)基礎篇

執行一條 select 語句&#xff0c;期間發生了什么&#xff1f; | 小林coding 目錄 1、連接MySQL服務器 2、查詢緩存 3、解析SQL語句 4、執行SQL語句 5、MySQL一行記錄的存儲結構 Server 層負責建立連接、分析和執行 SQL存儲引擎層負責數據的存儲和提取。支持InnoDB、MyIS…

IntelliJ IDEA 接入 AI 編程助手(Copilot、DeepSeek、GPT-4o Mini)

IntelliJ IDEA 接入 AI 編程助手&#xff08;Copilot、DeepSeek、GPT-4o Mini&#xff09; &#x1f4ca; 引言 近年來&#xff0c;AI 編程助手已成為開發者的高效工具&#xff0c;它們可以加速代碼編寫、優化代碼結構&#xff0c;并提供智能提示。本文介紹如何在 IntelliJ I…

2025.2.20總結

今晚評測試報告&#xff0c;評到一半&#xff0c;由于看板數據沒有分析完&#xff0c;最后讓我搞完再評. 盡管工作了多年的同事告訴我&#xff0c;活沒干完&#xff0c;差距比較大&#xff0c;沒資格評報告&#xff0c;但還是本著試試的態度&#xff0c;結果沒想到評審如此嚴苛…

ok113i——交叉編譯音視頻動態庫

提示&#xff1a;buildroot支持ffmpeg和SDL&#xff0c;但博主的ffmpeg是按下面方法編譯通過&#xff0c;SDL使用buildroot直接編譯也通過&#xff1b; 1. 下載ffmpeg源碼 下載鏈接&#xff1a;https://github.com/FFmpeg/FFmpeg/tags 根據版本需要自行下載壓縮包&#xff0c…

什么叫不可變數據結構?

不可變數據結構(Immutable Data Structures)是指一旦創建之后,其內容就不能被修改的數據結構。這意味著任何對不可變數據結構的“修改”操作實際上都會返回一個新的數據結構,而原始數據結構保持不變。 一、不可變數據結構的核心特點 不可變性:一旦創建后,數據結構的內容…

深度學習之圖像分類(一)

前言 圖像回歸主要是對全連接進行理解 而圖像分類任務主要是對卷積的過程進行理解 這一部分會介紹一些基礎的概念 卷積的過程&#xff08;包括單通道和多通道&#xff09; 理解一個卷積神經網絡工作的過程 以及常見的模型的類別和創新點 圖像分類是什么 定義 圖像分類是指將輸…