多線程安全問題原理和4種解決辦法

来源:https://www.cnblogs.com/huaweiyun/archive/2022/12/14/16982458.html
-Advertisement-
Play Games

摘要:多線程訪問了共用的數據,會產生線程安全問題。 本文分享自華為雲社區《多線程安全問題原理和解決辦法Synchronized和ReentrantLock使用與區別》,作者:共飲一杯無。 線程安全問題概述 賣票問題分析 單視窗賣票 一個視窗(單線程)賣100張票沒有問題單線程程式是不會出現線程安全問 ...


摘要:多線程訪問了共用的數據,會產生線程安全問題。

本文分享自華為雲社區《多線程安全問題原理和解決辦法Synchronized和ReentrantLock使用與區別》,作者:共飲一杯無。

線程安全問題概述

賣票問題分析

  • 單視窗賣票

一個視窗(單線程)賣100張票沒有問題
單線程程式是不會出現線程安全問題的

  • 多個視窗賣不同的票

3個視窗一起賣票,賣的票不同,也不會出現問題
多線程程式,沒有訪問共用數據,不會產生問題

  • 多個視窗賣相同的票

3個視窗賣的票是一樣的,就會出現安全問題
多線程訪問了共用的數據,會產生線程安全問題

線程安全問題代碼實現

模擬賣票案例

創建3個線程,同時開啟,對共用的票進行出售

public class Demo01Ticket {
 public static void main(String[] args) {
 //創建Runnable介面的實現類對象
 RunnableImpl run = new RunnableImpl();
 //創建Thread類對象,構造方法中傳遞Runnable介面的實現類對象
 Thread t0 = new Thread(run);
 Thread t1 = new Thread(run);
 Thread t2 = new Thread(run);
 //調用start方法開啟多線程
        t0.start();
        t1.start();
        t2.start();
 }
}
public class RunnableImpl implements Runnable{
 //定義一個多個線程共用的票源
 private int ticket = 100;
 //設置線程任務:賣票
 @Override
 public void run() {
 //使用死迴圈,讓賣票操作重覆執行
 while(true){
 //先判斷票是否存在
 if(ticket>0){
 //提高安全問題出現的概率,讓程式睡眠
 try {
 Thread.sleep(10);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 //票存在,賣票 ticket--
 System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票");
                ticket--;
 }
 }
 }
}

線程安全問題原理分析

線程安全問題產生原理圖

分析:線程安全問題正常是不允許產生的,我們可以讓一個線程在訪問共用數據的時候,無論是否失去了cpu的執行權;讓其他的線程只能等待,等待當前線程賣完票,其他線程在進行賣票。

解決線程安全問題辦法1-synchronized同步代碼塊

同步代碼塊:synchronized 關鍵字可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。

使用synchronized同步代碼塊格式:

synchronized(鎖對象){
可能會出現線程安全問題的代碼(訪問了共用數據的代碼)
}

代碼實現如下:

public class Demo01Ticket {
 public static void main(String[] args) {
 //創建Runnable介面的實現類對象
 RunnableImpl run = new RunnableImpl();
 //創建Thread類對象,構造方法中傳遞Runnable介面的實現類對象
 Thread t0 = new Thread(run);
 Thread t1 = new Thread(run);
 Thread t2 = new Thread(run);
 //調用start方法開啟多線程
        t0.start();
        t1.start();
        t2.start();
 }
}
public class RunnableImpl implements Runnable{
 //定義一個多個線程共用的票源
 private int ticket = 100;
 //創建一個鎖對象
 Object obj = new Object();
 //設置線程任務:賣票
 @Override
 public void run() {
 //使用死迴圈,讓賣票操作重覆執行
 while(true){
 //同步代碼塊
 synchronized (obj){
 //先判斷票是否存在
 if(ticket>0){
 //提高安全問題出現的概率,讓程式睡眠
 try {
 Thread.sleep(10);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 //票存在,賣票 ticket--
 System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票");
                    ticket--;
 }
 }
 }
 }
}

⚠️註意:

  1. 代碼塊中的鎖對象,可以使用任意的對象。
  2. 但是必須保證多個線程使用的鎖對象是同一個。
  3. 鎖對象作用:把同步代碼塊鎖住,只讓一個線程在同步代碼塊中執行。

同步技術原理分析

同步技術原理:

使用了一個鎖對象,這個鎖對象叫同步鎖,也叫對象鎖,也叫對象監視器

3個線程一起搶奪cpu的執行權,誰搶到了誰執行run方法進行賣票。

  • t0搶到了cpu的執行權,執行run方法,遇到synchronized代碼塊這時t0會檢查synchronized代碼塊是否有鎖對象

發現有,就會獲取到鎖對象,進入到同步中執行

  • t1搶到了cpu的執行權,執行run方法,遇到synchronized代碼塊這時t1會檢查synchronized代碼塊是否有鎖對象

發現沒有,t1就會進入到阻塞狀態,會一直等待t0線程歸還鎖對象,t0線程執行完同步中的代碼,會把鎖對象歸 還給同步代碼塊t1才能獲取到鎖對象進入到同步中執行

總結:同步中的線程,沒有執行完畢不會釋放鎖,同步外的線程沒有鎖進不去同步。

解決線程安全問題辦法2-synchronized普通同步方法

同步方法:使用synchronized修飾的方法,就叫做同步方法,保證A線程執行該方法的時候,其他線程只能在方法外等著。

格式:

public synchronized void payTicket(){
可能會出現線程安全問題的代碼(訪問了共用數據的代碼)
}

代碼實現:

 public /**synchronized*/ void payTicket(){
 synchronized (this){
 //先判斷票是否存在
 if(ticket>0){
 //提高安全問題出現的概率,讓程式睡眠
 try {
 Thread.sleep(10);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 //票存在,賣票 ticket--
 System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票");
                ticket--;
 }
 }
 }

分析:

定義一個同步方法,同步方法也會把方法內部的代碼鎖住,只讓一個線程執行。

同步方法的鎖對象是誰?

就是實現類對象 new RunnableImpl(),也是就是this,所以同步方法是鎖定的this對象。

解決線程安全問題辦法3-synchronized靜態同步方法

同步方法:使用synchronized修飾的方法,就叫做同步方法,保證A線程執行該方法的時候,其他線程只能在方法外等著。對於static方法,我們使用當前方法所在類的位元組碼對象(類名.class)。
格式:

public static synchronized void payTicket(){
可能會出現線程安全問題的代碼(訪問了共用數據的代碼)
}

代碼實現:

 public static /**synchronized*/ void payTicketStatic(){
 synchronized (RunnableImpl.class){
 //先判斷票是否存在
 if(ticket>0){
 //提高安全問題出現的概率,讓程式睡眠
 try {
 Thread.sleep(10);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 //票存在,賣票 ticket--
 System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票");
                ticket--;
 }
 }
 }

分析:

靜態的同步方法鎖對象是誰?

不能是this,this是創建對象之後產生的,靜態方法優先於對象

靜態方法的鎖對象是本類的class屬性–>class文件對象(反射)。

解決線程安全問題辦法4-Lock鎖

Lock介面中的方法:

  • public void lock() :加同步鎖。
  • public void unlock() :釋放同步鎖

使用步驟:

  1. 在成員位置創建一個ReentrantLock對象
  2. 在可能會出現安全問題的代碼前調用Lock介面中的方法lock獲取鎖
  3. 在可能會出現安全問題的代碼後調用Lock介面中的方法unlock釋放鎖

代碼實現:

public class RunnableImpl implements Runnable{
 //定義一個多個線程共用的票源
 private int ticket = 100;
 //1.在成員位置創建一個ReentrantLock對象
 Lock l = new ReentrantLock();
 //設置線程任務:賣票
 @Override
 public void run() {
 //使用死迴圈,讓賣票操作重覆執行
 while(true){
 //2.在可能會出現安全問題的代碼前調用Lock介面中的方法lock獲取鎖
 l.lock();
 try {
 //先判斷票是否存在
 if(ticket>0) {
 //提高安全問題出現的概率,讓程式睡眠
 Thread.sleep(10);
 //票存在,賣票 ticket--
 System.out.println(Thread.currentThread().getName() + "-->正在賣第" + ticket + "張票");
                    ticket--;
 }
 } catch (InterruptedException e) {
 e.printStackTrace();
 }finally {
 l.unlock();
 //3.在可能會出現安全問題的代碼後調用Lock介面中的方法unlock釋放鎖
 //無論程式是否異常,都會把鎖釋放
 }
 }
 }

分析:

java.util.concurrent.locks.Lock介面

Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。相比Synchronized,ReentrantLock類提供了一些高級功能,主要有以下3項:

1.等待可中斷,持有鎖的線程長期不釋放的時候,正在等待的線程可以選擇放棄等待,這相當於Synchronized來說可以避免出現死鎖的情況。通過lock.lockInterruptibly()來實現這個機制。

2.公平鎖,多個線程等待同一個鎖時,必須按照申請鎖的時間順序獲得鎖,Synchronized鎖非公平鎖,ReentrantLock預設的構造函數是創建的非公平鎖,可以通過參數true設為公平鎖,但公平鎖表現的性能不是很好。

公平鎖、非公平鎖的創建方式:

//創建一個非公平鎖,預設是非公平鎖
Lock lock = new ReentrantLock();
Lock lock = new ReentrantLock(false);
 //創建一個公平鎖,構造傳參true
Lock lock = new ReentrantLock(true);

3.鎖綁定多個條件,一個ReentrantLock對象可以同時綁定多個對象。ReenTrantLock提供了一個Condition(條件)類,用來實現分組喚醒需要喚醒的線程們,而不是像synchronized要麼隨機喚醒一個線程要麼喚醒全部線程。

ReentrantLock和Synchronized的區別

相同點:

  1. 它們都是加鎖方式同步;
  2. 都是重入鎖;
  3. 阻塞式的同步;也就是說當如果一個線程獲得了對象鎖,進入了同步塊,其他訪問該同步塊的線程都必須阻塞在同步塊外面等待,而進行線程阻塞和喚醒的代價是比較高的(操作系統需要在用戶態與內核態之間來回切換,代價很高,不過可以通過對鎖優化進行改善);

不同點:

 

點擊關註,第一時間瞭解華為雲新鮮技術~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 首發微信公眾號:SQL資料庫運維 原文鏈接:https://mp.weixin.qq.com/s?__biz=MzI1NTQyNzg3MQ==&mid=2247485212&idx=1&sn=450e9e94fa709b5eeff0de371c62072b&chksm=ea37536cdd40da7 ...
  • "天底下沒有完美的資料庫,也許Oracle是個例外”,前陣子幾個DBA在討論國產化替代時,有人就這麼說。確實是的,Oracle算是比較完美的資料庫產品了,不過現在很多用戶都在面臨從Oracle資料庫向其他資料庫遷移的問題。中國電信已經宣佈了今年年底前全線下架Oracle資料庫,全部用國產或者開源數據... ...
  • 華為應用市場在2022年HDC大會期間發佈了一款3D水流主題,基於華為HMS Core Scene Kit服務能力,展現立體靈動的水流島嶼,可跟隨用戶指尖實現實時流體波動效果,既趣味又解壓。 讓變幻莫測的物質來實現我們在影視和游戲等多種應用場景中的奇思妙想,從早期步驟繁重的特效製作演變到如今,已經有 ...
  • 本文是 CSS Houdini 之 CSS Painting API 系列第三篇。 現代 CSS 之高階圖片漸隱消失術 現代 CSS 高階技巧,像 Canvas 一樣自由繪圖構建樣式! 在上兩篇中,我們詳細介紹了 CSS Painting API 是如何一步一步,實現自定義圖案甚至實現動畫效果的! ...
  • 好家伙, 在上一篇中,我們知道了, JS的數組中每個槽位可以存儲任意類型的數據 那麼,我們能通過數組去模仿某些數據結構嗎? 答案是肯定的 1.棧方法 ECMAScript 給數組提供幾個方法,讓它看起來像是另外一種數據結構。 數組對象可以像棧一樣,也就是一種限制插人和刪除項的數據結構。 棧是一種後進 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 最近APP項目開發完成,在評審會上老闆提了一個需求,想在開發的APP上添加一個鏈接,可以跳轉公司的小程式商城。 原以為會很複雜,結果只有短短的幾行代碼。 plus.share.getServices(function(res){ var ...
  • 🏆一、HTML是什麼又不是什麼? 👤1.1、HTML是什麼 超文本標記語言(Hypertext Markup Language, HTML)是一種用於創建網頁的標記語言。 本質上是瀏覽器可識別的規則,我們按照規則寫網頁,瀏覽器根據規則渲染我們的網頁。對於不同的瀏覽器,對同一個標簽可能會有不同的解 ...
  • 前端界有兩個“教派”,一個叫 Vue,一個叫 React。Vue 的成員看不起 React,React 成員鄙視 Vue,他們認為手中的“教義”就是真理,可以消滅世界一切苦難。 但正如沒有絕對的真理,也沒有絕對完美的系統框架,我們需要一雙明辨是非的眼睛去解析所面對的難題,帶我們找到正確的方法,解決所... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...