【JavaEE】線程安全

【JavaEE】線程安全

  • 一、引出線程安全
  • 二、引發線程安全的原因
  • 三、解決線程安全問題
      • 3.1 synchronized關鍵字(解決修改操作不是原子的)
        • 3.1.1 synchronized的特性
        • 3.1.1 synchronized的使用事例
      • 3.2 volatile 關鍵字(解決內存可見性)
  • 四、死鎖
      • 4.1 可重入
      • 4.2 兩個線程出現的死鎖
      • 4.3 哲學家就餐問題
      • 4.4 造成死鎖的原因

博客結尾有此篇博客的全部代碼!!!

一、引出線程安全

舉例:

public class Demo1 {private 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();t1.join();t2.join();System.out.println("count="+count);//結果:56154}
}

這段代碼運行結束發現結果不是理論值:100000,而是每次運行完出現一個新的數。

在計算機操作系統中,count++;在寄存器中分為三步:讀取,加一,寫回這三步。

假設:兩個線程按照這樣進行,那么得到的count的最終值就是理論值100000。
在這里插入圖片描述
但往往事實不是這樣這樣的,CPU資源調度是隨機的!!!很有可能是這樣的(這里只列舉一種情況給大家示范一下):
在這里插入圖片描述
首先t1線程和t2 線程分別從內存中讀取count值,此時兩個線程讀取到的count值都是0,然后t1線程進行加一操作后寫回到內存中,t2線程也是進行加一操作后寫回到內存中,t2線程得到的count值將t1得到的count覆蓋,這樣count經過兩個線程的加一操作之后值還是1!
在這里插入圖片描述

二、引發線程安全的原因

  1. 【根本原因】操作系統對于線程的調度是隨機的,搶占式執行
  2. 多個線程同時修改同一變量
  3. 修改操作不是原子的(事務中的原子性)
  4. 內存可見性,引起的線程不安全
  5. 指令重排序,引起的線程不安全

三、解決線程安全問題

  • 由于線程調度是隨機的,這個不是我們可以左右的;
  • 我們確保多個線程不同時修改同一變量

主要帶大家學習引發第三個和第四個引起線程安全的解決方法:

3.1 synchronized關鍵字(解決修改操作不是原子的)

引發線程安全第三個原因是:修改操作不是原子的;關鍵字:synchronized將修改操作“鎖”在一起(相當于將讀取,加一,寫回三個操作綁定在一起,三操作要么全部執行,要么全部不執行)
在這里插入圖片描述

3.1.1 synchronized的特性
  1. 互斥
    synchronized 會起到互斥效果, 某個線程執?到某個對象的 synchronized 中時, 其他線程如果也執?到同?個對象 synchronized 就會阻塞等待.
    語法:
synchronized(變量){
//修改操作
}

在這里插入圖片描述

? 進? synchronized 修飾的代碼塊, 相當于 加鎖
? 退出 synchronized 修飾的代碼塊, 相當于 解鎖

public class Demo2 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {synchronized (lock){count++;}}}); Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (lock){count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println("count="+count);//結果:count=10000}
}

當加上鎖之后,count值就是10000!
這里需要注意的事:
synchronized(變量)里面的這個變量必須是相同的變量,否則就不會發生阻塞等待!!!
事例 :

public class Demo3 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Object lock1 = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {synchronized (lock1){count++;}}}); Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (lock2){count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println("count="+count);//結果:count=70738}}
  1. 可重入
    可重入就是指一個線程連續針對一個對象加多次鎖,不會出現“死鎖”現象稱為可重入。
synchronized (block) {synchronized(block) {//代碼} //右大括號}2
}  //右大括號}1

按理來說,在進入第一個synchronized的時候,加上了一把鎖,此時已經是“鎖定狀態”,當我們進入到第二個synchronized的時候要加鎖,就發生“阻塞等待”,就要等到第一個鎖走到右大括號}1解完鎖才能加,然而第一個鎖走到右大括號}1解鎖,又需要第二把鎖創建走完到右大括號}2。
這是線程就卡死了,這就是死鎖

Java大佬發現了這個問題,所以將synchronized設為可重入鎖,這樣就不會出現死鎖的問題。
? 如果某個線程加鎖的時候, 發現鎖已經被?占?, 但是恰好占?的正是??(這個鎖是自己加的), 那么仍然可以繼續獲取到鎖, 并讓計數器?增.
? 解鎖的時候計數器遞減為 0 的時候, 才真正釋放鎖. (才能被別的線程獲取到)

3.1.1 synchronized的使用事例
  1. 修飾代碼塊
    鎖定任意對象
    在這里插入圖片描述
    鎖住當前對象
     public class SynchronizedDemo {public void method() {synchronized (this) {}}}
  1. 直接修飾普通?法
public class SynchronizedDemo {public synchronized void methond() {}}
  1. 修飾靜態?法
public class SynchronizedDemo {public synchronized static void methond() {}}

3.2 volatile 關鍵字(解決內存可見性)

volatile可以保證內存可見性,只能修飾變量。并且volatile不能保證原子性

計算機運行代碼/程序的時候,訪問數據常常要從內存中訪問(定義變量時變量就儲存在內存中),然而CPU從內存中讀取數據相比于從寄存器中讀取數據要慢上很多(幾千上萬倍),CPU在進行讀/寫內存的時候速度就會降低。

為了解決這個問題,提高效率,編譯器就可能會對代碼優化,把一些本來要讀取內存的操作,優化為讀取寄存器,減少讀取內存的次數。這就會導致內存可見性問題。

以我們接下來的代碼為例------當CPU從自身寄存器中讀取成千上萬次發現count一直是0,此時編譯器就將代碼優化,讓count一直等于0,所以接下來線程1中一直處于循環狀態,盡管線程2中已經將count修改為1!

public class Demo4 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Object lock1 = new Object();Thread t1 = new Thread(() -> {while (count == 0) {}System.out.println("循環結束");});Thread t2 = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}count = 1;});t1.start();t2.start();System.out.println("main 線程結束");}
}

這里修改的方法:給線程1中的程序加入sleep,讓它休眠時間大于線程2的休眠時間,這樣它讀取的count就是1,編譯器就不會進行優化,循環就會結束!

Thread t1 = new Thread(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}while (count == 0) {}System.out.println("循環結束");});Thread t2 = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}count = 1;});

volatile解決內存可見性問題:

public class Demo5 {private volatile static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while (count == 0) {}System.out.println("循環結束");});Thread t2 = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}count = 1;});t1.start();t2.start();System.out.println("main 線程結束");}
}

四、死鎖

死鎖是一個非常嚴重的bug,它會讓你的代碼在執行到這塊卡住!!!

4.1 可重入

public class Demo6 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {synchronized (lock) {synchronized (lock) {count++;}}}System.out.println("循環結束");});t1.start();t1.join();System.out.println(count);}
}

由于Java提出了可重入的概念,所以這段代碼執行的到這里并沒有卡住!但在C++中,沒有引入可重入的概念,所以C++這里寫出這樣的代碼就會出現死鎖!!!

4.2 兩個線程出現的死鎖

假設t1線程先拿醋,t2線程先拿醬,兩個線程都將醋和醬已經加上自己的鎖了,然后t1線程嘗試拿醬,t2線程嘗試拿醋,此時就會出現死鎖!!!

public class Demo7 {public static void main(String[] args) throws InterruptedException {Object lock1 = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {synchronized (lock1) {System.out.println("t1拿到醋了!!!");synchronized (lock2) {System.out.println("t1拿到醬了!!!");}}});Thread t2 = new Thread(() -> {synchronized (lock2) {System.out.println("t2拿到醬了!!!");synchronized (lock1) {System.out.println("t2拿到醋了!!!");}}});t1.start();t2.start();t1.join();t2.join();System.out.println("main 線程結束!!!");}
}

如果將兩個鎖改成并列就不會出現死鎖!

     Thread t2 = new Thread(() -> {synchronized (lock2) {System.out.println("t2拿到醬了!!!");}synchronized (lock1) {System.out.println("t2拿到醋了!!!");}});

4.3 哲學家就餐問題

相當于是兩個線程出現死鎖的進階(M個線程,N把鎖):
5個哲學家(5個線程),5只筷子(5把鎖),哲學家坐在圓桌邊,桌上放有面條,每只筷子放在每個哲學家的中間。
在這里插入圖片描述

每個哲學家,會做兩件事:

  1. 思考人生.放下筷子,啥都不干
  2. 吃面條.拿起左右兩側的兩根筷子,開始吃面條,

哲學家啥時候吃面條,啥時候思考人生,是隨機的
哲學家吃面條啥時候吃完,也是隨機的,
哲學家正在吃面條的過程中,會持有左右兩側的筷子。此時相鄰的哲學家如果也想吃面條,就需要阻塞等待,
當出現極端情況,每個哲學家都想吃面條,都拿起自己左手邊的筷子,并且不會在沒吃到面條情況下放下筷子,這時就是死鎖了。

4.4 造成死鎖的原因

  • 互斥使用(鎖的基本特性):當一個線程拿到一把鎖后,另一個線程要拿到這把鎖就要阻塞等待;
  • 不可搶占(鎖的基本特性):當一把鎖被線程拿到后,其他線程不能搶占,只能等線程自己釋放鎖;
  • 請求保持(代碼結構):當一個線程拿到一把鎖后,再去拿其它鎖的時候,已經被拿到的鎖不會被釋放;
  • 循環/環路 等待(代碼結構):阻塞等待的依賴關系形成環了。

此篇博客的全部代碼!!!

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

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

相關文章

Vue核心知識:動態路由實現完整方案

在Vue中實現動態路由&#xff0c;并結合后端接口和數據庫表設計&#xff0c;是一個復雜的項目&#xff0c;需要多個技術棧和步驟的配合。以下將詳細描述整個實現過程&#xff0c;包括數據庫設計、后端接口設計、前端路由配置以及如何實現動態路由的功能。 目錄 一、需求分析二…

自媒體多賬號如何切換不同定位才能做得更好

一、選擇稀缺增長的賽道&#xff0c;避開內卷紅海 1.職場賽道 ● 細分方向&#xff1a;公務員/體制內經驗分享、自由職業指南、遠程辦公技巧。例如&#xff0c;通過采訪自由職業者或分享遠程工作體驗&#xff0c;快速積累精準粉絲。 ● 優勢&#xff1a;職場人群需求明確&…

基于SpringBoot的校園二手交易平臺(源碼+論文+部署教程)

運行環境 校園二手交易平臺運行環境如下&#xff1a; ? 前端&#xff1a;Vue ? 后端&#xff1a;Java ? IDE工具&#xff1a;IntelliJ IDEA&#xff08;可自行更換&#xff09; ? 技術棧&#xff1a;SpringBoot Vue MySQL 主要功能 校園二手交易平臺主要包含前臺和…

iPhone 鏡像 連接錯誤

重置連接 defaults delete com.apple.ScreenContinuity打開 iPhone 鏡像 參考 mac鏡像iPhone無法連接報錯個人經歷的 iPhone 鏡像 bug 與部分解決辦法

Qt基礎入門-詳解

前言 qt之路正式開啟 &#x1f493; 個人主頁&#xff1a;普通young man-CSDN博客 ? 文章專欄&#xff1a;C_普通young man的博客-CSDN博客 ? 本人giee: 普通小青年 (pu-tong-young-man) - Gitee.com 若有問題 評論區見&#x1f4dd; &#x1f389;歡迎大家點贊&#x1f44…

Unity 優化封裝常用API和編輯器擴展工具包

資源名&#xff1a;WXTools 文章目錄 MeshRenderEditorSpriteGroupToolWXEditorUtilsComponentUtilsDataUtilsGameObjectUtilsRigidbodyUtilsStringUtilsTransformUtilsVectorUtilsWXTools 內容包括&#xff1a; MeshRenderEditor mesh擴展 SpriteGroupTool SpriteGroup操作…

python學習第三天

條件判斷 條件判斷使用if、elif和else關鍵字。它們用于根據條件執行不同的代碼塊。 # 條件判斷 age 18 if age < 18:print("你還是個孩子&#xff01;") elif age 18:print("永遠十八歲&#xff01;") else:print("你還年輕&#xff01;")…

ThinkPHP使用phpword讀取模板word文件并添加表格

1.安裝phpword包composer require phpoffice/phpword 2.模板文件結構 如上圖框住的是要替換的文本和要復制表格樣式 實現代碼 <?phpnamespace app\api\logic;use PhpOffice\PhpWord\Element\Table; use PhpOffice\PhpWord\SimpleType\TblWidth; use PhpOffice\PhpWord\…

(原創)用python語言基于paddleocr構建批量識別實現紙質和電子的增值稅專用發票程序

文章目錄 1. 說明2. 準備工作3. 代碼3.1 導入庫&#xff1a;3.2 遍歷發票指定處理方式3.3 發票識別相關函數3.4 發票字段定位函數3.6 識別記錄相關函數3.6 識別結果校驗3.7 文件預處理等其他函數3.8 main主函數 1. 說明 1.1 以paddle識別引擎為基礎的增值稅發票識別程序&#…

DeepSeek搭配Excel,制作自定義按鈕,實現辦公自動化!

今天跟大家分享下我們如何將DeepSeek生成的VBA代碼&#xff0c;做成按鈕&#xff0c;將其永久保存在我們的Excel表格中&#xff0c;下次遇到類似的問題&#xff0c;直接在Excel中點擊按鈕&#xff0c;就能10秒搞定&#xff0c;操作也非常的簡單. 一、代碼準備 代碼可以直接詢問…

解決顯示器在高刷條件下花屏的問題

起因是家里的顯示器好久沒用之后&#xff0c;100HZ的刷新率下會花屏&#xff0c;在75HZ的情況下就正常顯示&#xff0c;在網上找了一圈感覺是硬件問題解決不了 于是想了想如果我用90HZ呢&#xff1f;不過原始的情況下沒有自定義刷新率的選擇&#xff0c;不過amd和nvida控制面板…

IP-----雙重發布

目錄 6.雙重發布 1.重發布的作用 2.部署條件 1.必須存在ASBR 2.種子度量值 3.重發布的規則 4.重發布的數量 5.重發布的場景 1.場景和規則 2.直連和靜態 3.動態RIP 4.動態OSPF 5.更改開銷值 6.重發布的問題1 7.重發布的問題2 1.流量 2.前綴列表 3.偏移列表 4…

藍橋杯試題:DFS回溯

一、題目要求 輸入一個數組n&#xff0c;輸出1到n的全排列 二、代碼展示 import java.util.*;public class ikun {static List<List<Integer>> list new ArrayList<>();public static void main(String[] args) { Scanner sc new Scanner(System.in);…

Ruby基礎

一、字符串 定義 283.to_s //轉為string "something#{a}" //定義字符串&#xff0c;并且插入a變量的值 something//單引號定義變量 %q(aaaaaaaaa) // 定義字符串&#xff0c;&#xff08;&#xff09;內可以是任何數&#xff0c;自動轉義雙引號%Q("aaaaa"…

基于提示驅動的潛在領域泛化的醫學圖像分類方法(Python實現代碼和數據分析)

摘要 醫學圖像分析中的深度學習模型易受數據集偽影偏差、相機差異、成像設備差異等導致的分布偏移影響&#xff0c;導致在真實臨床環境中診斷不可靠。領域泛化&#xff08;Domain Generalization, DG&#xff09;方法旨在通過多領域訓練提升模型在未知領域的性能&#xff0c;但…

C#—Settings配置詳解

C#—Settings配置詳解 在C#項目中&#xff0c;全局配置通常指的是應用程序的設置&#xff08;settings&#xff09;&#xff0c;這些設置可以跨多個類或組件使用&#xff0c;并且通常用于存儲應用程序的配置信息&#xff0c;如數據庫連接字符串、用戶偏好設置等。 Settings配置…

國自然面上項目|基于多模態MR影像的膠質母細胞瘤高危區域定位及預后預測研究|基金申請·25-02-28

小羅碎碎念 今天和大家分享一個面上項目&#xff0c;執行年限為2019.01&#xff5e;2022.12&#xff0c;直接費用為57萬元。 膠質母細胞瘤&#xff08;GBM&#xff09;預后差且差異大&#xff0c;異質性是重要因素&#xff0c;臨床手段難評價。影像組學為異質性研究提供方法&am…

Nat Mach Intell | AI分子對接算法評測

《Nature Machine Intelligence》發表重磅評測&#xff0c;系統評估AI與物理方法在虛擬篩選&#xff08;VS&#xff09;中的表現&#xff0c;突破藥物發現效率瓶頸。 核心評測體系&#xff1a;三大數據集 研究團隊構建了三個新型測試集&#xff1a; TrueDecoy&#xff1a;含14…

安路FPGA開發入門:軟件安裝與點燈與仿真(TangDynasty ModelSim)

文章目錄 前言軟件安裝開發軟件仿真軟件 點燈測試代碼編寫與編譯引腳分配固件下載 仿真測試ModelSim添加仿真庫TangDynasty仿真設置進行仿真 后記 前言 最近因為工作需要用安路的FPGA&#xff0c;這里對安路FPGA開發相關流程做個記錄。作為測試只需要一個核心板&#xff08;我這…

千峰React:外部庫引用

flushSync強制刷新 如果不強制刷新是這樣&#xff1a;每次count在下一輪才更新 import { useState, useRef } from react import { flushSync } from react-domfunction App() {const [count, setCount] useState(0)const refuseRef(null)const handleClick () > { setCo…