線程安全問題(二)——死鎖

死鎖

  • 前言
  • 可重入鎖
    • 邏輯
  • 兩個線程兩把鎖(死鎖)
  • 死鎖的特點
  • 多個線程多把鎖(哲學家就餐問題)
  • 總結


前言

在前面的文章中,介紹了鎖的基本使用方式——鎖

在上一篇文章中,通過synchronized關鍵字進行加鎖操作,使得【多線程修改同一變量】的情況可以得到解決。
那么在本文中,將會繼續講解鎖的相關知識點。


可重入鎖

我們可以通過synchronized確定鎖對象,對線程進行加鎖的操作。那么如果鎖對象重復使用是否會出現不一樣的結果?
在下面的案例中,t1和t2線程使用了連續synchronized,設置的加鎖對象同樣是counter,如果運行這段代碼,結果卻是正確的。
理論上,當synchronized(counter)開始使用時,只有執行完其中的代碼(大括號中的代碼塊)才會釋放鎖。而這個鎖中又嵌套了相同的鎖,按道理來說此時counter鎖對象還沒有被釋放,應該出現阻塞等待狀態最終代碼無法運行才是。
那么為什么在Java中這段代碼可以編譯通過?
原因: 在Java中,已經對synchronized內部進行了特殊處理。在每個鎖對象中,會記錄當前是哪個線程持有這把鎖,接下來,當針對這個對象進行加鎖操作的時候就會進行判定,判定當前嘗試加鎖線程是否是這一對象鎖的線程。 通過這樣的操作,系統就可以知道如果不是,就會阻塞;如果是,就會直接放行。

class Counter {public static int count;void add(){count++;}public int getCount(){return count;}
}public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread t1 = new Thread(()-> {for (int i = 0; i < 50000; i++) {synchronized (counter) {synchronized (counter) {counter.add();}}}});Thread t2 = new Thread(()-> {for (int i = 0; i < 50000; i++) {synchronized (counter) {synchronized (counter) {counter.add();}}}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = "+counter.getCount());}

邏輯

當加了多層鎖的時候,代碼如何知道執行到哪里要真正進行解鎖。如果有若干層加鎖操作,如何判定當前遇到的}是最外層的} ?
以我的理解,進行加鎖操作時在內部給鎖對象設計了一個計數器(int n)。 每次遇到【{ 】時,n++,遇到【 } 】時,n–,當n=0時才真正解鎖。
通過這樣的方式,針對同一線程中的同一鎖對象進行的鎖操作,可以讓程序猿避免了死鎖的情況,我們也稱之為可重入鎖。

兩個線程兩把鎖(死鎖)

現在存在線程t1和線程t2;鎖對象A和B。
當進行鎖操作的時候,可能會出現這樣的情況:線程1和線程2都需要使用到鎖A和鎖B,對于線程A來說,首先獲取鎖A然后獲取鎖B;而對于B來說,首先獲取鎖B再獲取鎖A。
讓兩個線程同時獲得第一把鎖,接下來需要嘗試去獲取對方的鎖。

在下面的代碼啟動后,線程t1獲取到了鎖locker1,線程t2獲取到了鎖locker2.
對于t1,接下來需要獲取locker2才能繼續執行接下來的代碼
對于t2,需要獲取locker1才能繼續執行接下來的代碼

兩個線程之間無法讓步,于是一同進入了阻塞等待狀態,都在等待對方釋放鎖。

public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(()-> {synchronized (locker1){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){System.out.println("獲取到了2把鎖");}}});Thread t2 = new Thread(()-> {synchronized (locker2){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker1){System.out.println("獲取到了2把鎖");}}});t1.start();t2.start();}

在執行這段代碼后,我們可以通過jconsole查看線程狀態。如下圖所示,我們可以知道兩個線程都處于阻塞狀態,同時我們也可以知道阻塞所需要獲取的鎖目前在哪個線程身上。
在這里插入圖片描述
在這里插入圖片描述

死鎖的特點

1.鎖具有互斥性。這是鎖的基本特點,當一個線程拿到鎖A時,其他線程只能等待該線程釋放。
2.鎖不可搶占。只有該線程主動釋放鎖,別的線程無法搶占。
3.請求和保持。線程拿到鎖以后可以繼續嘗試獲取其他鎖。
4.循環等待。多個線程多個鎖的狀態中,出現了A等待B,B等待A的情況。
當全部滿足這些條件以后,就可以發生死鎖。

多個線程多把鎖(哲學家就餐問題)

情景:在一個餐桌上存在五個哲學家,他們做兩件事情:一是思考哲學,二是就餐。
每個哲學家左右手各有一根筷子以供就餐時使用。如果哲學家手中只有一根筷子,他會等到另一根筷子擁有的時候才會就餐,而不會放下筷子。
在這里插入圖片描述
通過這個情景,我們可以很明顯的預知到一種情況:所有的哲學家手中都拿起左邊的筷子,于是所有哲學家都停下了,進入阻塞等待狀態。
我們通過這個問題可以反映到線程的情況,因為多線程多鎖的原因,如果沒有合理安排則會導致線程阻塞甚至死鎖!

解決這種情況的一種辦法就是約定好加鎖的順序,破除循環等待的情況。
在下面的代碼中,兩個線程輪流獲取locker1和locker2,這就可以有效規避死鎖的問題了。

    public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(()-> {synchronized (locker1){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){System.out.println("獲取到了2把鎖");}}});Thread t2 = new Thread(()-> {synchronized (locker1){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){System.out.println("獲取到了2把鎖");}}});t1.start();t2.start();}

總結

死鎖在多線程中是及其常見的一個問題,導致了線程的不安全。是我們要極力規避的情況。我們了解了死鎖發生的情況,死鎖的原因等多個點。
本文使用源碼? 源碼

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

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

相關文章

XML簡介XML 使用教程XML的基本結構XML的使用場景

學習總結 1、掌握 JAVA入門到進階知識(持續寫作中……&#xff09; 2、學會Oracle數據庫入門到入土用法(創作中……&#xff09; 3、手把手教你開發炫酷的vbs腳本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA編程利器技巧(編寫中……&#xff09; 5、面經吐血整理的 面試技…

VMware每次打開網絡設置都出現需要運行NetworkManager問題

每次打開都出現這個情況&#xff0c;是因為之前把NetworkManager服務服務關閉&#xff0c;重新輸入命令&#xff1a; sudo systemctl start NetworkManager.service或者 sudo service network-manager restart 即可解決&#xff0c;但是每次開機重啟都要打開就很麻煩&#xf…

【Chapter4】匯編語言及其程序設計,《微機系統》第一版,趙宏偉

一、匯編語言概述 **指令&#xff1a;**指使計算機完成某種操作的命令。 **程序&#xff1a;**完成某種功能的指令序列。 **軟件&#xff1a;**各種程序總稱。 **機器語言&#xff1a;**計算機能直接識別的語言。用機器語言寫出的程序稱為機器代碼。 **匯編語言&#xff1…

Forecasting from LiDAR via Future Object Detection

Forecasting from LiDAR via Future Object Detection 基礎信息 論文&#xff1a;cvpr2022paper https://openaccess.thecvf.com/content/CVPR2022/papers/Peri_Forecasting_From_LiDAR_via_Future_Object_Detection_CVPR_2022_paper.pdfgithub&#xff1a;https://github.co…

SyncUnsafeCell替換Mutex提高性能

1. 背景 在Rust開發過程中&#xff0c;很多情況下需要在不可變的情況下獲取可變性或者在多線程的情況下可以安全的貢獻可變數據。這種情況下我們一般使用**Mutex來實現通過加鎖來實現。現在我們可以通過使用SyncUnsafeCell來替代Mutex**。 2. SyncUnsafeCell SyncUnsafeCell…

【計算機網絡——1.1網絡internet】

網絡 就是用通信線路和通信設備把很多個“主機/端設備“相互聯系。然后按照某種溝通方式&#xff0c;專業術語叫“協議”&#xff0c;共享信息。 **&#xff08; 計算機網絡&#xff1a;節點和邊構成的系統 節點&#xff1a; 主機節點&#xff1a;主機/端設備(手機&#x…

K8S之網絡深度剖析(一)(持續更新ing)

K8S之網絡深度剖析 一 、關于K8S的網絡模型 在K8s的世界上,IP是以Pod為單位進行分配的。一個Pod內部的所有容器共享一個網絡堆棧(相當于一個網絡命名空間,它們的IP地址、網絡設備、配置等都是共享的)。按照這個網絡原則抽象出來的為每個Pod都設置一個IP地址的模型也被稱作為I…

SpringBoot(一)創建一個簡單的SpringBoot工程

Spring框架常用注解簡單介紹 SpringMVC常用注解簡單介紹 SpringBoot&#xff08;一&#xff09;創建一個簡單的SpringBoot工程 SpringBoot&#xff08;二&#xff09;SpringBoot多環境配置 SpringBoot&#xff08;三&#xff09;SpringBoot整合MyBatis SpringBoot&#xff08;四…

3.ROS串口實例

#include <iostream> #include <ros/ros.h> #include <serial/serial.h> #include<geometry_msgs/Twist.h> using namespace std;//運行打開速度控制插件&#xff1a; rosrun rqt_robot_steering rqt_robot_steering //若串口訪問權限不夠&#xff1a…

詳解PEFT庫中LoRA源碼

前言 GitHub項目地址Some-Paper-CN。本項目是譯者在學習長時間序列預測、CV、NLP和機器學習過程中精讀的一些論文&#xff0c;并對其進行了中文翻譯。還有部分最佳示例教程。如果有幫助到大家&#xff0c;請幫忙點亮Star&#xff0c;也是對譯者莫大的鼓勵&#xff0c;謝謝啦~本…

讀書筆記-《Spring技術內幕》(三)MVC與Web環境

前面我們學習了 Spring 最核心的 IoC 與 AOP 模塊&#xff08;讀書筆記-《Spring技術內幕》&#xff08;一&#xff09;IoC容器的實現、讀書筆記-《Spring技術內幕》&#xff08;二&#xff09;AOP的實現&#xff09;&#xff0c;接下來繼續學習 MVC&#xff0c;其同樣也是經典…

Spring底層原理之bean的加載方式八 BeanDefinitionRegistryPostProcessor注解

BeanDefinitionRegistryPostProcessor注解 這種方式和第七種比較像 要實現兩個方法 第一個方法是實現工廠 第二個方法叫后處理bean注冊 package com.bigdata1421.bean;import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.…

解決idea中git無法管理項目中所有需要管理的文件

點擊文件->設置 選擇版本控制—>目錄映射 點擊加號 設置整個項目被Git管理

【python入門】自定義函數

文章目錄 定義自定義函數的基本語法參數類型示例代碼函數作用域匿名函數&#xff08;Lambda&#xff09;閉包裝飾器 Python中的自定義函數允許你編寫一段可重用的代碼塊&#xff0c;這段代碼可以帶參數&#xff08;輸入&#xff09;&#xff0c;并可能返回一個值&#xff08;輸…

MySQL高級-事務-并發事務演示及隔離級別

文章目錄 0、四種隔離級別1、創建表 account2、修改當前會話隔離級別為 read uncommitted2.1、會出現臟讀 3、修改當前會話隔離級別為 read committed3.1、可以解決臟讀3.2、會出現不可重復讀 4、修改當前會話隔離級別為 repeatable read&#xff08;默認&#xff09;4.1、解決…

解決docker鏡像pull失敗的有效

機器環境 本實踐將在 Ubuntu 22.04.3LTS 系統上進行測試 docker 版本Docker Engine - Community 24.0.6 &#xff0c;原則上docker版本無影響 本實踐進僅學習研究使用&#xff0c;無作他用途。 背景 曾幾何時&#xff0c;docker鏡像的拉去會失敗&#xff0c;網速會慢&#xff0…

代碼隨想錄算法訓練營第五十三天| 739. 每日溫度、 496.下一個更大元素 I、503.下一個更大元素II

LeetCode 739. 每日溫度 題目鏈接&#xff1a;https://leetcode.cn/problems/daily-temperatures/description/ 文章鏈接&#xff1a;https://programmercarl.com/0739.%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6.html 思路 * 單調棧的本質是空間換時間&#xff0c;因為在遍歷的過…

【論文閱讀】transformer及其變體

寫在前面&#xff1a; transformer模型已經是老生常談的一個東西&#xff0c;以transformer為基礎出現了很多變體和文章&#xff0c;Informer、autoformer、itransformer等等都是頂刊頂會。一提到transformer自然就是注意力機制&#xff0c;變體更是數不勝數&#xff0c;一提到…

【目標檢測】DN-DETR

一、引言 論文&#xff1a; DN-DETR: Accelerate DETR Training by Introducing Query DeNoising 作者&#xff1a; IDEA 代碼&#xff1a; DN-DETR 注意&#xff1a; 該算法是在DAB-DETR基礎上的改進&#xff0c;在學習該算法前&#xff0c;建議掌握DETR、DAB-DETR等相關知識…

TCP和UDP的區別以及應用場景

TCP&#xff08;傳輸控制協議&#xff09;和UDP&#xff08;用戶數據報協議&#xff09;是兩種不同的傳輸層協議 區別 TCP是面向連接的&#xff0c;UDP是無連接的&#xff1b; TCP是可靠的&#xff0c;UDP是不可靠的&#xff1b; TCP是面向字節流的&#xff0c;UDP是面向數據…