那曲檬骨新材料有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

在多線程的情況下如何對一個值進行 a++ 操作

科技綠洲 ? 來源:Java技術指北 ? 作者:Java技術指北 ? 2023-10-13 11:17 ? 次閱讀

在多線程的情況下,對一個值進行 a++ 操作,會出現什么問題?

a++ 的問題

先寫個 demo 的例子。把 a++ 放入多線程中運行一下。定義 10 個線程,每個線程里面都調用 5 次 a++,把 a 用 volatile 修飾,可以讓 a 的值在修改之后,所有的線程立刻就可以知道。最后結果是不是 50,還是其他的數字?

public class Test {

    private static volatile  int a = 0;

    public static void main(String[] args) {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(new Runnable(){

                @Override
                public void run() {
                   try {
                        for(int j = 0; j < 10; j++) {
                            System.out.print(a++ + ", ");
                            Thread.sleep(100);
                        }
                    } catch (Exception e) {

                    }
                }
            });
            threads[i].start();
        }
    }
}

圖片

從結果上看 a++ 的操作并沒有達到預期值的 50,而是少了很多,其中還有一定是有問題的。那就是因為 a++ 的操作并不是原子性的。

原子性

并發編程,有三大原則:有序性、可見性、原子性

  1. 有序性:正常編譯器執行代碼,是按順序執行的。有時候,在代碼順序對程序的結果沒有影響時,編譯器可能會為了性能從而改變代碼的順序。
  2. 可見性:一個線程修改了一個變量的值,另外一個線程立刻可以知道修改后的值。
  3. 原子性:一個操作或者多個操作在執行的時候,要么全部被執行,要么全部都不執行。

上面的 a++ 就沒有原子性,它有三個步驟:

  1. 在內存中讀取了 a 的值。
  2. 對 a 進行了 + 1 操作。
  3. 將新的 a 值刷回到內存。

這三個步驟可以被示例中的 10 個線程上下文切換打斷:當 a = 10

  1. 線程 1 將 a 的值讀取到內存, a = 10
  2. 線程 2 將 a 的值讀取到內存, a = 10
  3. 線程 1 將 a + 1,a = 11
  4. 此時線程發生切換,線程 2 對 a 進行 + 1 操作, a = 11
  5. 線程 2 將 a 的值寫回到內存, a = 11
  6. 線程 1 將 a 的值寫回到內存, a = 11

從上面的步驟中可以看出 a 的值在兩次相加后沒有得到 12 的值,而是 11。這就是 a++ 引發的問題。

小 B 把上面的步驟對面試官講了一遍,面試官又問了,有什么方式可以避免這個問題,小 B 不加思索的回答用 synchronized 加鎖。面試官說 synchronized 太重了,還有其他的解決方式嗎?小 B 暈了。其實可以使用 AtomicInteger 的 incrementAndGet() 方法。

AtomicInteger 源碼分析

主要屬性

首先看看 AtomicInteger 的主要屬性。

//sun.misc 下的類,提供了一些底層的方法,用于和操作系統交互
private static final Unsafe unsafe = Unsafe.getUnsafe();
// value 字段的內存地址相對于對象內存地址的偏移量
private static final long valueOffset;
//通過 unsafe 初始化 valueOffset,獲取偏移量
static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}

// 用 valatile 修飾的值,保證了內存的可見性
private volatile int value;

從屬性中可以看出 AtomicInteger 調用的是 Unsafe 類,Unsafe 類中大多數的方法是用 native 修飾的,可以直接進行一些系統級別的操作。

用 volatile 修飾 value 值,保證了一個線程的值對另外一個線程立即可見。

incrementAndGet()

//AtomicInteger.incrementAndGet()
public final int incrementAndGet() {
    //調用 unsafe.getAndAddInt()
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

//Unsafe.getAndAddInt()
//參數:需要操作的對象,偏移量,要增加的值
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

//Unsafe.compareAndSwapInt()
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

incrementAndGet() 首先獲取了當前值,然后調用 compareAndSwapInt() 方法更新數據。

compareAndSwapInt() 是 CAS 的縮寫來源,比較并替換。被 native 修飾,調用了操作系統底層的方法,保證了硬件級別的原子性。

var2,var4,var5 是它的三個操作數,表示內存地址偏移量 valueOffset,預期原值 expect,新的值 update。把 this.compareAndSwapInt(var1, var2, var5, var5 + var4) 變成 this.compareAndSwapInt(obj, valueOffset, expect, update),釋義就是如果內存位置中的 valueOffset 值 與 expect 的值相同,就把內存中的 valueOffset 改成 update,否則不操作。

getAndAddInt() 方法中用了 do-while,就相當于如果 CAS 一直更新不成功,就不退出循環。直到更新成功為止。

ABA 問題

CAS 操作也并不是沒有問題的。

  1. 循環操作時間長了,開銷大。用了 do-while,如果更新一直不成功,就一直在循環。會給 CPU 帶來很大的開銷。
  2. 只能保證一個共享變量的原子性。循環 CAS 的方式只能保證一個變量進行原子操作,在對多個變量進行 CAS 的時候就沒辦法保證原子性了。
  3. ABA 問題。CAS 的操作一般是 1. 讀取內存偏移量 valueOffset。2. 比較 valueOffset 和 expect 的值。3. 更新 valueOffset 的值。如果線程 A 讀取 valueOffset 后,線程 B 修改了 valueOffset 的值,并且將 valueOffset 的值又改了回來。線程 A 會認為 valueOffset 的值并沒有改變。這就是 ABA 問題。要解決這個問題,就是在每次修改 valueOffset 值的時候帶上一個版本號。

總結

這篇文章介紹了 CAS,它是 java 中的樂觀鎖,每次認為操作并不會有其他線程去修改數據,如果有其他線程操作了數據,就重試,一直到成功為止。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 編程
    +關注

    關注

    88

    文章

    3637

    瀏覽量

    93989
  • 多線程
    +關注

    關注

    0

    文章

    278

    瀏覽量

    20075
  • 代碼
    +關注

    關注

    30

    文章

    4828

    瀏覽量

    69058
收藏 人收藏

    評論

    相關推薦

    Java多線程的用法

    本文將介紹一下Java多線程的用法。 基礎介紹 什么是多線程 指的是進程中同時運行多個
    的頭像 發表于 09-30 17:07 ?1006次閱讀

    多線程編程之: 問題提出

    多線程編程之 問題提出編寫耗時的單線程程序:  新建
    發表于 10-22 11:41

    LabView的多線程語言

    LabView的多線程語言以前只會照貓畫虎的寫些簡單的程序,些基本原理不是很清晰。從網上找了些資料,這里總結一下。1。
    發表于 06-08 10:13

    基于51單片機的多線程操作系統 精選資料分享

    我知道,51單片機上運行操作系統,大多數情況下并不實用。但51單片機廣為人知。所以我認為,用它來逐步的實現
    發表于 07-20 07:55

    如何使用多線程和異步操作等并發設計方法來最大化程序的性能

      因為異步操作無須額外的線程負擔,并且使用回調的方式進行處理,設計良好的情況下,處理函數可以不必使用共享變量(即使無法完全不用,最起碼可
    發表于 08-23 16:31

    MCU開發中使用多線程操作讀是否需要保護?

    ,那么多線程訪問是安全的,那么對于讀,某些情況下需要保護,某些情況下其實可以不需要保護。
    發表于 02-01 15:42

    很多變量多線程讀寫是使用關中斷好還是使用互斥進行保護呢?

    我想問一下,就是我有很多變量會多線程讀寫操作,有些會比較頻繁,我讀寫的時候是使用中斷去保護還是增加互斥量去保護。 1.如果加互斥量,當前低優先級讀寫
    發表于 05-05 14:14

    QNX環境多線程編程

    介紹了QNX 實時操作系統和多線程編程技術,包括線程間同步的方法、多線程程序的分析步驟、線程基本程序結構以及實用編譯方法。QNX 是由加拿大
    發表于 08-12 17:37 ?30次下載

    基于多線程環境的遞增操作--原子操作

    因此多線程環境中對變量進行讀寫時,我們需要有種方法能夠保證對
    的頭像 發表于 01-10 11:16 ?6219次閱讀
    基于<b class='flag-5'>多線程</b>環境<b class='flag-5'>下</b><b class='flag-5'>值</b>的遞增<b class='flag-5'>操作</b>--原子<b class='flag-5'>操作</b>

    Linux多線程編程

    線程呢?使用多線程到底有哪些好處?什么的系統應該選用多線程?我們首先必須回答這些問題。  使用多線程的理由之是和進程相比,它是
    發表于 04-02 14:43 ?636次閱讀

    怎樣才能在不加鎖的情況下解決多線程問題

    我們知道,多線程同時修改共享變量時會出現數據不致的問題,比如多個線程同時對變量加1,假設count的初始
    的頭像 發表于 03-02 09:31 ?515次閱讀
    怎樣才能在不加鎖的<b class='flag-5'>情況下</b>解決<b class='flag-5'>多線程</b>問題

    基于QT自制上位機(多線程

    前言:應用程序某些情況下需要處理比較復雜的邏輯,例如常規的圖傳上位機,如果在傳輸圖片跑到較高碼流或對圖像執行些處理任務是,引用多線程可以明顯 改善響應度和反饋速度。 QT
    發表于 05-09 11:47 ?1次下載
    基于QT自制上位機(<b class='flag-5'>多線程</b>)

    多線程事務怎么回滾?簡單示例演示多線程事務

    spring中可以使用@Transactional注解去控制事務,使出現異常時會進行回滾,多線程中,這個注解則不會生效,如果主線程需要先
    發表于 08-09 12:22 ?697次閱讀
    <b class='flag-5'>多線程</b>事務怎么回滾?<b class='flag-5'>一</b><b class='flag-5'>個</b>簡單示例演示<b class='flag-5'>多線程</b>事務

    什么情況下避免使用系統調用

    linux多線程環境對同變量進行讀寫時,經常會遇到讀寫的原子性問題,即會出現競爭條件。為了解決多個
    的頭像 發表于 11-13 10:32 ?496次閱讀
    什么<b class='flag-5'>情況下</b>避免使用系統調用

    c語言中a++是什么意思

    C語言中,a++自增運算符,用于對a進行
    的頭像 發表于 11-26 09:19 ?1.9w次閱讀
    百家乐官网赌场视屏| 大发888充值100元| 巫溪县| 2024年九运的房屋风水吉凶| 澳门玩百家乐00| 百家乐官网分析软件骗人| 百家乐电投网站| 百家乐官网技术秘籍| 百家乐龙虎桌布| 百家乐官网庄闲几率| 波浪百家乐测试| 新时代娱乐城开户| 7月24日风水| 战神国际| 百家乐高手和勒威| 青海省| 赌百家乐怎样能赢| 贵南县| 现场百家乐官网能赢吗| 红利来娱乐城| 百家乐可以算牌么| 百家乐官网连黑记录| 网上百家乐记牌软件| 玩百家乐官网游戏经验| 百家乐怎么玩| 闲和庄百家乐官网赌场娱乐网规则| 大发888游戏平台dafa 888 gw| 迪士尼百家乐官网的玩法技巧和规则 | 百家乐官网平注7s88| 大发888m摩卡游戏| 百家乐最新套路| 百家乐官网怎么玩了| 好运来百家乐的玩法技巧和规则| 百家乐官网制胜软件| 互联星空棋牌中心| 百家乐棋牌外挂| 怎样看百家乐官网路纸| 皇冠现金网安全吗| 稳赢的百家乐投注方法| 太阳城百家乐官网手机投注| 大发888娱乐场下载地址|