【JavaEE】多線程 -- CAS機制(比較并交換)

目錄

  • CAS是什么
  • CAS的應用
    • 實現原子類
    • 實現自旋鎖
  • ABA問題
    • ABA問題概述
    • ABA問題引起的BUG
    • 解決方案

CAS是什么

  • CAS (compare and swap) 比較并交換,CAS 是物理層次支持程序的原子操作。說起原子性,這就設計到線程安全問題,在代碼的層面為了解決多線程并發處理同一共享資源造成的線程安全問題,我們常常會使用 synchronized 修飾代碼塊,變量等,將程序背后的指令封裝成原子性(事務的最小單位,不可再分),當一個線程執行 synchronized 修飾的代碼塊時獲取指定對象的對象鎖(一個對象只有一把鎖),其他并發處理同一代碼塊的線程因無法獲取對象鎖就會進入阻塞等待對象鎖釋放,然后繼續競爭對象鎖,此時 synchronized 修飾的代碼塊就具有原子性,具有互斥性,不可搶占性。
  • CAS 是CPU 物理層次支持的原子操作(一條指令).

讀取內存數據(V),預期原值(A)和新值(B)。
如果內存位置的值與預期原值相等,就將新值(B)更新到內存中,替換掉原內存數據,
如果內存位置的值與預期原值不相等,處理器不會做任何操作,
CAS 對數據操作后會返回一個 boolean 值判斷是否成功。

  • 以上指令集合,可以視為CPU 物理層次支持的一條指令,同樣可以解決多線程并發處理同一共享資源造成的線程安全問題。

CAS 使用Java偽代碼理解含義:

// address 原內存值
// expectValue 舊的預期值
// swapValue 需要修改的新值
// & 代表取地址,這里主要是理解這層含義,java 語法不支持
boolean CAS(address, expectValue, swapValue) {if (&address == expectedValue) {&address = swapValue;return true;}return false;
}

在這里插入圖片描述

  • 這么表示, 那么我們最終關心的是內存中的值, 至于寄存器的值一般都不要了

CAS的應用

  • 操作系統會對 CPU 指令進行封裝,JVM 又會對操作系統提供的 API 再進行一層封裝,由于 CAS 本身就是 CPU 指令,所以在 Java 中也有關于 CAS 的 API,關于 CAS 的 API 放在了 unsafe 類里,Java 的標準庫中又對 CAS 進行了進一步的封裝,并且提供了一些工具類,可以讓我們直接使用。

實現原子類

  • 原子類,就是 Java 標準庫對 CAS 進行進一步封裝后提供的一種工具類,如下圖所示:
    在這里插入圖片描述
  • 在原子類中可以看到,它對 Integer 和 Long 進行了封裝,此時針對這樣的對象再進行多線程修改,就是線程安全的了,不知道大家還記不記得前面介紹線程安全時的一個代碼示例,就是利用兩個線程分別對同一個變量分別進行自增 50000 次,當時用這個代碼示例來演示了線程不安全的效果,后來我們通過加鎖的方式解決了這樣的問題,下面我就來使用原子類來寫一個用兩個線程對同一變量分別進行 50000 次自增的代碼,來看看此時會不會出現線程安全問題,代碼及運行結果如下所示:
public class demo36 {private static AtomicInteger count = new AtomicInteger();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for(int i = 0; i < 50000; i++) {count.getAndIncrement(); //前綴自增//count.incrementAndGet(); //后綴自增}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {// count++;count.getAndIncrement();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}

在這里插入圖片描述

  •  如上圖所示,使用原子類不會出現之前的問題,這是因為之前使用的 result++ 是三個指令,在多線程中的三個指令會穿插執行,所以會引起線程不安全,此處的 getAndIncrement 對變量的修改是一個 CAS 指令,天生就是原子的,就不會出現穿插執行這種問題了,并且這個代碼不涉及加鎖,不需要阻塞等待,更高效。
    
  • 那么上面的這段代碼是如何做到把自增操作變成原子的呢?我們可以進入源碼來嘗試找尋一下答案,如下圖所示:
    在這里插入圖片描述
  • 層層點入到了native修飾的代碼, 表示是C++寫的代碼, 我們無法直接能看見. 這里我們用偽代碼來理解一下邏輯
// 下面代碼是一段偽代碼(不能編譯執行,只是用來表示邏輯)
class AtomicInteger {// value 表示我們要進行自增操作的變量private int value;public int getAndIncrement() {// oldValue 表示放到寄存器中的值int oldValue = value;// CAS 進行比較交換while (CAS(value, oldValue, oldValue + 1) != true) {// value 與 oldValue 的值不相等,說明在此期間有其他線程修改了 value 的值// 更新 oldValue 的值與 value 相等,再次進行循環oldValue = value;}// 當 value 與 oldValue 相等,就將 oldValue+1 的值賦值給 value// 以此來實現 value 的自增操作return Value;}
}

在這里插入圖片描述

  • 可以看到我們判斷和賦值都是一條指令, 這里線程的隨機調度并不能把判斷和賦值分開. 這就保證了我們這里比較和交換是原子的. 和我們不加鎖比較和交換指令被線程調度插隊不一樣, 也就保證了我們線程安全.
  • 可以看到我們使用CAS后這個線程是允許t2線程指令插隊在t1線程指令前面的, 個時候他插隊了那么肯定內存的值和寄存器的值一定不一樣這, 那么我們就需要重新加載寄存器的值到內存中讓寄存器和內存中的值保證一樣. 這就和我們加鎖不讓t2線程插隊, 執行的結果一樣了.
  • 為了確保當前讀取到的值是內存中最新值付出的代價就是“自旋”,如果不是最新值就不斷的循環判斷,直到是最新值后再進行修改,此時就會消耗更多的 CPU 資源。

實現自旋鎖

在這里插入圖片描述

  • 自旋鎖一般用于鎖競爭不激烈的情況下,在上述代碼中,當 owner 不為 null 的時候,循環就會一直執行,通過這樣的“忙等”來完成等待的效果,此處自旋式的等沒有放棄 CPU,不會參與到調度中,省去了調度開銷,但是會消耗更多 CPU 的資源。

ABA問題

ABA問題概述

在這里插入圖片描述

  • 圖中的場景就是t1線程想把num值改成100, 這個時候執行過程如下
  1. 先讀取 num 的值,記錄到 oldNum 變量中;
  2. 使用 CAS 判斷當前 num 的值是否為 0,如果為 0 就修改成 100。
  • 但是在這個時候, t1線程修改到num之前, t2線程優先被調度了, t2線程把num修改到100后, 又修改為0 .這個時候t1線程希望改的num是沒有變過的, 結果t2改變了又改回去了. t1線程就無法判斷到底這個num值有沒有被改動過.這種問題就好比我們買了一款新手機,無法分辨這是由別人使用過重新翻新的還是他原本就是新的。

ABA問題引起的BUG

在這里插入圖片描述

解決方案

  • 我們從上面的例子中看見, ABA問題出現的核心原因就是另一個線程對同一個變量進行修改, 進行改動(如加操作), 然后又改回去(如減操作), 那么當前線程就并不知道他是進行了對這個變量進行改動(已經執行了減操作)的. 所以再去進行減操作.
  • 為了解決這個問題. 我們約定不允許進行又進行加操作, 又進行減操作.這個時候另外一個線程對一個變量進行改動(如加操作), 就不可能還原原來變量的值(如減操作)對于本身就必須雙向變化的數據,可以引入一個版本號,版本號這個數字只能增加不能減少,此時就可以根據版本號來判斷當前數字是不是第一次被修改。
  • 這個時候我們再進行上面的扣款操作

t1線程獲取到當前余額是1000, 版本號為1, 期望扣款500. 也就是把余額修改為500. 這個時候調度到t2線程
t2線程獲取當前余額是1000, 版本號為2, 余額修改為500(扣款成功). 調度到t3線程
t3線程獲取當前余額是500, 版本號為3, 想對余額里面轉賬500. 修改余額為1000. 調度到t1
t1線程當前余額為1000, 與之前獲取的余額相同. 但是當前版本號為3, 與之前的版本號1不同. 這個時候期望扣款500就失敗了.

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

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

相關文章

The United Nations Is Already Dead

The United Nations Is Already Dead When children in Gaza rummage through rubble for food, when UN-run schools are reduced to dust, when the Security Council cannot even pass the mildest ceasefire resolution—blocked by a single veto— we must confront a br…

Kubernetes v1.34 前瞻:資源管理、安全與可觀測性的全面進化

預計正式發布&#xff1a;2025年8月底 | 分類&#xff1a;Kubernetes 隨著2025年8月底的臨近&#xff0c;Kubernetes社區正緊鑼密鼓地準備下一個重要版本——v1.34的發布。本次更新并非簡單的功能疊加&#xff0c;而是在資源管理、安全身份、可觀測性和工作負載控制等核心領域的…

用 Bright Data MCP Server 構建實時數據驅動的 AI 情報系統:從市場調研到技術追蹤的自動化實戰

前言 本文通過兩個真實場景&#xff08;云服務商對比與 AIGC 技術追蹤&#xff09;&#xff0c;展示了如何使用 Bright Data MCP Server 與 Lingma IDE 構建一個具備實時網頁數據抓取、結構化分析與自動化報告生成能力的 AI 工作流。通過簡單的 API 調用與 JSON 配置&#xff…

牛頓第二定律的所有表達方式:1、線性表達 2、圓形表達 3、雙曲線表達 4、拋物線表達5、數列表達

牛頓第二定律是經典力學中的核心定律&#xff0c;表述為&#xff1a;物體的加速度與所受合力成正比&#xff0c;與質量成反比&#xff0c;方向與合力方向相同。其基本矢量形式為&#xff1a; F?ma? \vec{F} m \vec{a} Fma 其中&#xff0c;F?\vec{F}F 是合力&#xff08;單…

【開發日記】SpringBoot 實現支持多個微信小程序的登錄

在實際業務場景中&#xff0c;需要一個后臺同時支持多個微信小程序的登錄。例如&#xff0c;企業有多個不同業務的小程序&#xff0c;但希望統一在同一個后臺系統里進行用戶認證和數據處理。這時候&#xff0c;我們就需要一個靈活的方式來管理多個小程序的 appid 和 secret&…

Docker 容器(一)

Docker一、Docker是什么1.什么是Docker2.Docker特點3.比較虛擬機和容器二、Docker安裝1.Docker??三大核心組件??2.安裝步驟&#xff08;Ubuntu&#xff09;3.阿里云鏡像加速三、Docker鏡像1.什么是鏡像2.UnionFS&#xff08;聯合文件系統&#xff09;3.Docker鏡像加載原理4…

容器安全實踐(二):實踐篇 - 從 `Dockerfile` 到 Pod 的權限深耕

在上一篇《容器安全實踐&#xff08;一&#xff09;&#xff1a;概念篇》中&#xff0c;我們深入探討了容器安全的底層原理&#xff0c;并糾正了“容器天生安全”的誤解。我們了解了 root 用戶的雙重身份&#xff0c;以及特權容器的危險性。 然而&#xff0c;僅僅了解這些概念…

c#_數據持久化

數據持久化架構 數據是應用程序的命脈。持久化架構的選擇直接決定了應用的性能、可擴展性、復雜度和維護成本。本章將深入探討.NET生態中主流的數據訪問模式、工具和策略&#xff0c;幫助你為你的系統做出最明智的數據決策。5.1 ORM之爭&#xff1a;Entity Framework Core深度剖…

996引擎-骰子功能

996引擎-骰子功能 測試NPC QF回調函數 結果 參考資料 在測試NPC播放骰子動畫。 播放前需要先設置骰子點數 測試NPC [[骰子的顯示順序和點數 對應 私人變量 D0 D1 D2 D3 D4 D5]] -- NPC入口函數 function main(player)-- 骰子共6個,設置骰子點數后,再執行搖骰子,否則沒動畫…

Vue 3多語言應用開發實戰:vue-i18n深度解析與最佳實踐

&#x1f4d6; 概述 Vue 3 國際化&#xff08;i18n&#xff09;是構建多語言應用的核心需求。本文檔介紹 Vue 3 中實現國際化的主流方案&#xff0c;包括 vue-i18n、Vite 插件方案和自定義解決方案。 &#x1f3af; 主流方案對比 方案優點缺點適用場景vue-i18n功能完整、生態成…

港口船舶流量統計準確率↑27%!陌訊多模態融合算法實戰解析

一、行業痛點&#xff1a;港口船舶流量統計的三大核心難題智慧港口建設中&#xff0c;船舶流量統計是泊位調度、航道管理與安全預警的核心數據支撐&#xff0c;但傳統方案受場景特性限制&#xff0c;長期存在難以解決的技術瓶頸。據《2023 年中國港口智能化發展報告》顯示&…

Shell腳本的基礎知識學習

Shell 腳本是 Linux/Unix 系統的核心自動化工具&#xff0c;能夠完成以下任務&#xff1a; &#xff08;1&#xff09;批量操作&#xff1a;一鍵安裝軟件、批量處理文件&#xff08;重命名、壓縮、備份等&#xff09;。 &#xff08;2&#xff09;系統管理&#xff1a;監控資源…

k8s部署,pod管理,控制器,微服務,集群儲存,集群網絡及調度,集群認證

k8s部署 k8s中容器的管理方式 ? Kubernetes集群創建方式 centainerd 默認情況下&#xff0c;K8S在創建集群時使用的方式 docker docker使用的普記錄最高&#xff0c;雖然K8S在1.24版本后已經費力了kubelet對docker的支持&#xff0c;但時可以借助cri-docker方式來實現集…

JAVA限流方法

在 Java 項目中限制短時間內的頻繁訪問&#xff08;即接口限流&#xff09;&#xff0c;是保護系統資源、防止惡意攻擊或高頻請求導致過載的重要手段。常見實現方案可分為單機限流和分布式限流&#xff0c;以下是具體實現方式&#xff1a;一、核心限流算法無論哪種方案&#xf…

性能比拼: .NET (C#) vs. Fiber (Go)

本內容是對知名性能評測博主 Anton Putra .NET (C#) vs. Fiber (Go): Performance (Latency - Throughput - Saturation - Availability) 內容的翻譯與整理, 有適當刪減, 相關指標和結論以原作為準 在本視頻中&#xff0c;我們將對比 C# 與 .NET 框架和 Golang 的表現。在第一個…

信譽代幣的發行和管理機制是怎樣的?

信譽代幣的發行與管理機制是區塊鏈技術與經濟模型深度融合的產物&#xff0c;其核心在于通過代碼和社區共識構建可量化、可驗證的信任體系。以下從技術架構、經濟模型、治理機制三個維度展開分析&#xff0c;并結合具體案例說明&#xff1a;一、發行機制&#xff1a;行為即價值…

神經網絡|(十二)概率論基礎知識-先驗/后驗/似然概率基本概念

【1】引言 前序學習進程中&#xff0c;對貝葉斯公式曾經有相當粗糙的回歸&#xff0c;實際上如果我們看教科書或者網頁&#xff0c;在講貝葉斯公式的時候&#xff0c;會有幾個名詞反復轟炸&#xff1a;先驗概率、后驗概率、似然概率。 今天就來把它們解讀一下&#xff0c;為以…

使用UE5開發《紅色警戒3》類戰略養成游戲的硬件配置指南

從零開始&#xff0c;學習 虛幻引擎5&#xff08;UE5&#xff09;&#xff0c;開始游戲開發之旅&#xff01;本文章僅提供學習&#xff0c;切勿將其用于不法手段&#xff01;開發類似《紅色警戒3》級別的戰略養成游戲&#xff0c;其硬件需求遠超普通2D或小型3D項目——這類游戲…

Vue2+Vue3前端開發_Day12-Day14_大事件管理系統

參考課程: 【黑馬程序員 Vue2Vue3基礎入門到實戰項目】 [https://www.bilibili.com/video/BV1HV4y1a7n4] ZZHow(ZZHow1024) 項目收獲 Vue3 composition APIPinia / Pinia 持久化處理Element Plus&#xff08;表單校驗&#xff0c;表格處理&#xff0c;組件封裝&#xff09…

[ACTF新生賽2020]明文攻擊

BUUCTF在線評測BUUCTF 是一個 CTF 競賽和訓練平臺&#xff0c;為各位 CTF 選手提供真實賽題在線復現等服務。https://buuoj.cn/challenges#[ACTF%E6%96%B0%E7%94%9F%E8%B5%9B2020]%E6%98%8E%E6%96%87%E6%94%BB%E5%87%BB下載查看&#xff0c;一個壓縮包和一張圖片。壓縮包需要密…