併發編程之無鎖

来源:https://www.cnblogs.com/haizai/archive/2020/02/16/12318635.html
-Advertisement-
Play Games

併發編程之無鎖 6.2 CAS與volatile 源碼之LongAdder 6.8 Unsafe 6.2 CAS與volatile 其中的關鍵是compareAndSet,它的簡稱就是CAS(也有Compare And Swap的說法),它必須是原子操作。註意其實CAS的底層是lock cmpxch ...


併發編程之無鎖

 

6.2 CAS與volatile

在這裡插入圖片描述
其中的關鍵是compareAndSet,它的簡稱就是CAS(也有Compare And Swap的說法),它必須是原子操作。
在這裡插入圖片描述
註意
其實CAS的底層是lock cmpxchg指令(X86架構),在單核CPU和多核CPU下都能夠保證【比較-交換】的原子性。

  • 在多核狀態下,某個核執行到帶lock的指令時,CPU會讓匯流排鎖住,當這個核把此指令執行完畢,再開啟匯流排。這個過程中不會被線程的調度機制所打斷,保證了多個線程對記憶體操作的準確性,是原子的。
    volatile
    獲取共用變數時,為了保證變數的可見性,需要使用volatile修飾。
    它可以用來修飾成員變數和靜態成員變數,他可以避免線程從自己的工作緩存中查找變數的值,必須到主存中獲取它的值,線程操作volatile變數都是直接操作主存。即一個線程對volatile變數的修改,對另一個線程可見。
    註意 :
    volatile僅僅保證了共用變數的可見性,讓其它線程能夠看到最新值,但不能解決指令交錯問題(不能保證原子性)
    CAS必須藉助volatile才能讀取到共用變數的最新值來實現【比較並交換】的效果
    為什麼無鎖效率高
  • 無鎖情況下,即使重試失敗,線程始終在高速運行,沒有停歇,而synchronized會讓線程在沒有獲得鎖的時候,發生上下文切換,進入阻塞。打個比喻
  • 線程就好像高速跑道上的賽車,高速運行時,速度超快,一旦發生上下文切換,就好比賽車要減速、熄火,等被喚醒又得重新打火、啟動、加速…恢復到高速運行,代價比較大
  • 但無鎖情況下,因為線程要保存運行,需要額外CPU的支持,CPU在這裡就好比高速跑道,沒有額外的跑道,線程想高速運行也無從談起,雖然不會進入阻塞,但由於沒有分到時間片,任然會進入可運行狀態,還是會導致上下文切換。
    CAS的特點
    結合CAS和volatile可以實現無鎖併發,適用於線程數少、多核CPU的場景下。
  • CAS是基於樂觀鎖的思想 :最客觀的估計,不怕別的線程來修改共用變數,就算改了也沒關係,我吃虧點再重試。
  • synchronized是基於悲觀鎖的思想 :最悲觀的估計,提防著其他線程來修改共用變數,我上了鎖你們都別想改,我改完瞭解開鎖,你們才有機會。
  • CAS體現的是無鎖併發、無阻塞併發
    • 因為沒有使用synchronized,所以線程不會陷入阻塞,這是效率提升的因素之一
    • 但如果競爭激烈,可以想到重試必然頻繁發生,反而效率會受影響
package com.example.demo;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class Test4 {
    public static void main(String[] args) {
        demo(
                () -> new AtomicLong(0),
                (adder) -> adder.getAndIncrement()
        );
        
        demo(
                () -> new LongAdder(),
                (adder) -> adder.increment()
        );
    }

    /**
     *
     * @param adderSupplier : () -> 結果   提供累加器對象
     * @param action        : (參數) -> void  執行累加操作
     * @param <T>
     */
    private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
        T adder = adderSupplier.get();
        List<Thread> threadList = new ArrayList<>();
        // 4個線程,每人累加50萬
        for (int i = 0; i < 4; i++) {
            threadList.add(new Thread(() -> {
                for (int j = 0; j < 500000; j++) {
                    action.accept(adder);
                }
            }));
        }
        long start = System.nanoTime();
        threadList.forEach(t -> t.start());
        long end = System.nanoTime();
        System.out.println(adder + " cost : " + (end - start));
    }
}
package com.example.demo;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

public class Test3 {

    public static void main(String[] args) {

    }

    /**
     * 參數1,提供數組,可以是線程不安全數組或線程安全數組
     * 參數2,獲取數組長度的方法
     * 參數3,自增方法,回傳array,index
     * 參數4,列印數組的方法
     *
     * @param arraySupplier : 提供者  無中生有  () -> 結果
     * @param lengthFun    : 函數    一個參數一個結果  (參數) -> 結果 ,BiFunction(參數1,參數2) -> 結果
     * @param putConsumer  : 消費者  (參數1,參數2) -> void
     * @param printConsumer : 消費者  (參數) -> void
     * @param <T>
     */
    private static <T> void demo(
            Supplier<T> arraySupplier,
            Function<T, Integer> lengthFun,
            BiConsumer<T, Integer> putConsumer,
            Consumer<T> printConsumer
    ) {
        List<Thread> threadList = new ArrayList<>();
        T array = arraySupplier.get();
        Integer length = lengthFun.apply(array);
        for (int i = 0; i < length; i++) {
            // 每個線程對數組做10000次操作
            threadList.add(new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    putConsumer.accept(array, j % length);
                }
            }));
        }
        threadList.forEach(thread -> thread.start());
    }

}

輸出
在這裡插入圖片描述
性能提升的原因很簡單,就是在有競爭時,設置多個累加單元,Thread-0累加 Cel【0】,而Thread-1累加Cell【1】。。。最後將結果彙總。這樣它們在累加時操作的不同的Cell變數,因此減少了CAS重試失敗,從而提高性能。

源碼之LongAdder

LongAdder類有幾個關鍵域
在這裡插入圖片描述

  • 原理之偽共用
    其中Cell即為累加單元
    在這裡插入圖片描述
    緩存與記憶體的速度比較
    在這裡插入圖片描述
    在這裡插入圖片描述
    因為CPU與記憶體的速度差異很大,需要靠預讀數據至緩存來提升效率。
    而緩存以緩存行為單位,每個緩存行對應著一塊記憶體,一般是64byte(8個long)
    緩存的加入會造成數據副本的產生,即同一份數據會緩存在不同核心的緩存行中,CPU要保證數據的一致性,如果某個CPU核心更改了數據其它CPU核心對應的整個緩存行必須失效
    在這裡插入圖片描述
    因為Cell是數組形式,在記憶體中是連續存儲的,一個Cell為24位元組(16位元組的對象頭和8位元組的value),因此緩存行可以存下2個的Cell對象。這樣問題來了 :
  • Core-0要修改Cell【0】
  • Core-1要修改Cell【1】
    無論誰修改成功,都會導致對方Core的緩存行失效,比如Core-0中Cell【0】=6000,Cell【1】=8000 要累加Cell【0】=6001,Cell【1】=8000,這時會讓Core-1的緩存行失效
    @sun.misc.Contended用來解決這個問題,它的原理是在使用此註解的對象或欄位的前後各增加128位元組大小的padding,從而讓CPU將對象預讀至緩存時占用不同的緩存行,這樣,不會造成對方緩存行的失效
    在這裡插入圖片描述
    在這裡插入圖片描述
    在這裡插入圖片描述
    在這裡插入圖片描述
    在這裡插入圖片描述
    在這裡插入圖片描述
    獲取最終結果通過sun方法
    在這裡插入圖片描述

6.8 Unsafe

概述
Unsafe對象提供了非常底層的,操作記憶體、線程的方法,Unsafe對象不能直接調用,只能通過反射獲得
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述


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

-Advertisement-
Play Games
更多相關文章
  • 資料庫設置 在上一章節中學習瞭如何創建Django項目,在Django項目中創建web應用,以及如何在Django主程式的URL中引用web應用中的URL。下麵來瞭解如何在Django中使用資料庫。Django中想要使用資料庫, 首先要瞭解mysite/mysite/settings.py中關於數據 ...
  • 需求 maven依賴 列印sql 配置要點: 1. 驅動配置 application.properties 2. psy配置 aop列印持久層執行時間 使用aop實現; 啟用aop註解: 小結 來個效果截圖: 通過本片文章,你可以學會: 1. 給代碼添加aop切麵,增加日誌或者列印出方法執行總耗時; ...
  • 一、問題剖析 看到這個問題,我想吹水兩句再做推薦。一般發出這個疑問都處在初學編程階段,編程語言都是相通的,只要你領悟了一門語言的“任督二脈”,以後你學哪一門語言都會輕易上手。學語言嘛,當你工作一兩年了,你還真會覺得像當初老師說的那樣,語言只是工具罷了。工作期間,可能要你接觸到其它語言,而且要你能快速 ...
  • 生產者是如何生產消息 如何創建生產者 發送消息到Kafka 生產者配置 分區 ...
  • GUI編程 組建 視窗 彈窗 面板 文本框 列表框 按鈕 圖片 監聽事件 滑鼠 鍵盤事件 破解工具 1、簡介 GUI的核心技術:Swing AWT 為什麼不流行? 界面不美觀。 需要jre環境。(沒必要為一個5M的小游戲下載幾百M的jre) 但是學了java的GUI編程,有助於瞭解MVC架構,瞭解監 ...
  • 字元和字元串是最常用的信息 1:char表示字元 字元常量-兩個單引號中間的字元表示字元常量'A' 2:字元串和String 字元串常量-雙引號中間的字元序列"Java" 字元串常量是String型的實例的引用。String s="ABC"與String s=new String("ABC"); 3 ...
  • javaSE學習筆記(16) 網路編程 基本概念 如今,電腦已經成為人們學習、工作、生活必不可少的工具。我們利用電腦可以和親朋好友網上聊天,也可以玩網游、發郵件等等,這些功能實現都離不開電腦網路。電腦網路實現了不同電腦之間的通信,這必須依靠編寫網路程式來實現。下麵,我們將教大家如何編寫網路 ...
  • 通常,我們在寫java程式的時候,似乎很少關註記憶體分配和垃圾回收的問題。因為,這部分工作,JVM已經幫我們自動實現了。 這樣看起來,好像很美好,但是任何事情都有兩面性。雖然JVM會自動的進行垃圾回收,但是,如果遇到有些問題,JVM自己也處理不了呢? 因此,我們需要瞭解一下JVM垃圾回收是怎樣運作的, ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...