最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java并發(fā)中的ABA問題學(xué)習(xí)與解決方案

 更新時間:2022年05月06日 09:32:31   作者:FserSuN  
這篇文章主要介紹了Java并發(fā)中的ABA問題學(xué)習(xí)與解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

1.簡介

我們將了解在并發(fā)編程中的ABA問題。同時學(xué)習(xí)引起該問題的根因及問題解決辦法。

2.Compare and swap

為了理解根本原因,首先回顧一下Compare and swap的概念。Compare and Swap (CAS)在無鎖算法中是一種常見的技術(shù)。能夠保證并發(fā)修改共享數(shù)據(jù)時,一個線程將共享內(nèi)存修改后,另一線程嘗試對共享內(nèi)存的修改會失敗。

我們每次更新時,通過兩種信息來實現(xiàn):要更新的值及原始值。首先Compare and swap 會比較原始值和當(dāng)前獲取到的值。如果相等,那么將值更新為要設(shè)置的值。

3. ABA問題

當(dāng)執(zhí)行campare and swap會出現(xiàn)失敗的情況。例如,一個線程先讀取共享內(nèi)存數(shù)據(jù)值A(chǔ),隨后因某種原因,線程暫時掛起,同時另一個線程臨時將共享內(nèi)存數(shù)據(jù)值先改為B,隨后又改回為A。隨后掛起線程恢復(fù),并通過CAS比較,最終比較結(jié)果將會無變化。這樣會通過檢查,這就是ABA問題。 在CAS比較前會讀取原始數(shù)據(jù),隨后進行原子CAS操作。這個間隙之間由于并發(fā)操作,最終可能會帶來問題。

3.1 ABA問題的實際場景:賬戶余額修改

為了通過實例演示ABA問題。我們創(chuàng)建一個銀行賬戶類,該類維護一個整型變量記錄賬戶余額。該類有兩個函數(shù):一個用于存錢,一個用于取錢。這些操作使用CAS來修改賬戶余額。

3.2 賬戶余額修改時產(chǎn)生的問題

我們來考慮兩個線程操作同一個賬戶時的場景。當(dāng)線程1取錢時,先讀取余額,隨后通過CAS操作進行比較。然后,可能由于某些原因,線程1可能發(fā)生阻塞。與此同時,線程2同樣通過CAS機制,在線程1掛起時,在同一個賬戶上執(zhí)行兩個操作。首先,改變原始值,這個值已經(jīng)被線程1在剛才讀取。隨后線程2又將這個值改為原始值。

一旦線程1恢復(fù)后,在線程1看來,沒有發(fā)生任何變化。cas將會執(zhí)行成功。

在這里插入圖片描述

4.銀行取款問題代碼演示

創(chuàng)建一個Account類,balance記錄賬戶余額。transactionCount記錄成功執(zhí)行的事務(wù)數(shù)。currentThreadCASFailureCount來記錄CAS操作失敗的次數(shù)。

接著我們實現(xiàn)一個存款的方法deposit,與取款方法withdraw。為了演示ABA問題,同時實現(xiàn)一個maybeWait方法進行延遲等待。

最終的代碼如下:

    public class Account {
        private AtomicInteger balance;
        private AtomicInteger transactionCount;
        private ThreadLocal<Integer> currentThreadCASFailureCount;
        public Account() {
            this.balance = new AtomicInteger(0);
            this.transactionCount = new AtomicInteger(0);
            this.currentThreadCASFailureCount = new ThreadLocal<>();
            this.currentThreadCASFailureCount.set(0);
        }
        public int getBalance() {
            return balance.get();
        }
        public int getTransactionCount() {
            return transactionCount.get();
        }
        public int getCurrentThreadCASFailureCount() {
            return Optional.ofNullable(currentThreadCASFailureCount.get()).orElse(0);
        }
        public boolean withdraw(int amount) {
            int current = getBalance();
            maybeWait();
            boolean result = balance.compareAndSet(current, current - amount);
            if (result) {
                transactionCount.incrementAndGet();
            } else {
                int currentCASFailureCount = currentThreadCASFailureCount.get();
                currentThreadCASFailureCount.set(currentCASFailureCount + 1);
            }
            return result;
        }
        private void maybeWait() {
            if ("thread1".equals(Thread.currentThread().getName())) {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        public boolean deposit(int amount) {
            int current = balance.get();
            boolean result = balance.compareAndSet(current, current + amount);
            if (result) {
                transactionCount.incrementAndGet();
            } else {
                int currentCASFailureCount = currentThreadCASFailureCount.get();
                currentThreadCASFailureCount.set(currentCASFailureCount + 1);
            }
            return result;
        }
    }

接著我們對上述代碼進行測試。通過maybeWait方法,模擬出現(xiàn)ABA問題。

    @Test
    public void abaProblemTest() throws InterruptedException {
        final int defaultBalance = 50;
        final int amountToWithdrawByThread1 = 20;
        final int amountToWithdrawByThread2 = 10;
        final int amountToDepositByThread2 = 10;
        Assert.assertEquals(0, account.getTransactionCount());
        Assert.assertEquals(0, account.getCurrentThreadCASFailureCount());
        account.deposit(defaultBalance);
        Assert.assertEquals(1, account.getTransactionCount());
        Thread thread1 = new Thread(() -> {
            // this will take longer due to the name of the thread
            Assert.assertTrue(account.withdraw(amountToWithdrawByThread1));
            // thread 1 fails to capture ABA problem
            Assert.assertNotEquals(1, account.getCurrentThreadCASFailureCount());
        }, "thread1");
        Thread thread2 = new Thread(() -> {
            Assert.assertTrue(account.deposit(amountToDepositByThread2));
            Assert.assertEquals(defaultBalance + amountToDepositByThread2, account.getBalance());
            // this will be fast due to the name of the thread
            Assert.assertTrue(account.withdraw(amountToWithdrawByThread2));
            // thread 1 didn't finish yet, so the original value will be in place for it
            Assert.assertEquals(defaultBalance, account.getBalance());
            Assert.assertEquals(0, account.getCurrentThreadCASFailureCount());
        }, "thread2");
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        // compareAndSet operation succeeds for thread 1
        Assert.assertEquals(defaultBalance - amountToWithdrawByThread1, account.getBalance());
        //but there are other transactions
        Assert.assertNotEquals(2, account.getTransactionCount());
        // thread 2 did two modifications as well
        Assert.assertEquals(4, account.getTransactionCount());
    }

5.值類型與引用類型的場景

上面的例子中使用了getBalance()方法獲取了一個值類型數(shù)據(jù)。由于使用的是值類型,雖然出現(xiàn)ABA問題,但未對結(jié)果造成影響。如果我們操作的是引用類型,那么最終會保存不同的引用對象,會帶來意外的結(jié)果。

對于引用類型,下面以鏈棧為例說明。

  • 線程A希望將A結(jié)點出棧,此時讀取棧頂元素A,準(zhǔn)備執(zhí)行CAS操作,此時由于某種原因阻塞。
  • 線程B開始執(zhí)行,執(zhí)行出棧A、B。隨后將D、C、A結(jié)點壓入棧中。
  • 線程A恢復(fù)執(zhí)行。接著執(zhí)行CAS,比較發(fā)現(xiàn)棧頂結(jié)點A沒有被修改。隨后將棧頂結(jié)點改為B。由于B線程在第二步時,已經(jīng)將B結(jié)點移除,A線程修改后發(fā)生錯誤。棧的結(jié)構(gòu)發(fā)生破壞。

在這里插入圖片描述

接著我們通過下面的代碼進行演示:

    static class Stack {
        private AtomicReference<Node> top = new AtomicReference<>();
        static class Node {
            String value;
            Node next;
            public Node (String value) {
                this.value = value;
            }
        }
        //出棧
        public Node pop(int time) {
            Node newTop;
            Node oldTop;
            do {
                oldTop = top.get();
                if (oldTop == null) {
                    return null;
                }
                newTop = oldTop.next;
                try {
                    //休眠一段時間,模擬ABA問題
                    TimeUnit.SECONDS.sleep(time);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } while (!top.compareAndSet(oldTop, newTop));
            return oldTop;
        }
        public void push (Node node) {
            Node oldTop;
            do {
                oldTop = top.get();
                node.next = oldTop;
            } while (!top.compareAndSet(oldTop, node));
        }
        public AtomicReference<Node> getTop() {
            return top;
        }
    }
    @Test
    public void testStack() throws Exception{
        Stack stack = new Stack();
        Stack.Node a = new Stack.Node("A");
        Stack.Node b = new Stack.Node("B");
        // 初始化棧結(jié)構(gòu)
        stack.push(b);
        stack.push(a);
        // ABA 測試
        Thread t1 = new Thread(() -> {
            stack.pop(2);
        });
        
        Stack.Node c = new Stack.Node("C");
        Stack.Node d = new Stack.Node("D");
        Thread t2 = new Thread(() -> {
            stack.pop(0);
            stack.pop(0);
            stack.push(d);
            stack.push(c);
            stack.push(a);
        });
        //
        t1.start();
        t2.start();
        TimeUnit.SECONDS.sleep(5);
        Stack.Node top = stack.getTop().get();
        do {
            System.out.println(top.value);
            top = top.next;
        } while (top != null);
    }

6. 解決方法

  • hazard pointer:首先出現(xiàn)問題是因為,多個線程操作共享數(shù)據(jù),并未感知到別的線程正在對共享數(shù)據(jù)進行操作。通過hazard pointer介紹[1],其基本思想就是每個線程維護一個操作列表,在操作一個結(jié)點時將其記錄。如果一個線程要做結(jié)點變更,先搜索線程操作列表,看是否有其它線程操作。如果有則此次操作執(zhí)行失敗。
  • 不變性:從上述棧的例子中可以看到,在對結(jié)點A進行比較時,由于A依然是多個線程共享并復(fù)用,因此CAS會成功。如果每次操作時,新創(chuàng)建對象而不是復(fù)用。這樣CAS就會正常提示失敗。但這樣可能會創(chuàng)建大量對象。

7. Java中的解決方法

Java中提供了兩個類來解決這個問題。

  • AtomicStampedReference
  • AtomicMarkableReference

在原有類的基礎(chǔ)上,除了比較與修改期待的值外,增加了一個時間戳。對時間戳也進行CAS操作。這也稱為雙重CAS。從上例中看到。每次修改一個結(jié)點,其時間戳都發(fā)生變化。這樣即使共享一個復(fù)用結(jié)點,最終CAS也能返回正常的結(jié)果。

8. 總結(jié)

本文介紹了CAS產(chǎn)生ABA問題的背景,通用解決辦法及Java中的解決辦法。對于值類型有時發(fā)生ABA問題可能并不會造成問題。但對于引用類型,就可能造成歧義,同時破壞數(shù)據(jù)結(jié)構(gòu)。通過鏈棧的演示,我們可以有所了解ABA產(chǎn)生的問題。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Window中安裝構(gòu)建神器Jenkins詳解

    Window中安裝構(gòu)建神器Jenkins詳解

    Jenkins是一款開源 CI&CD 軟件,用于自動化各種任務(wù),包括構(gòu)建、測試和部署軟件。支持各種運行方式,可通過系統(tǒng)包、Docker 或者通過一個獨立的 Java 程序。是解放人工集成部署的自動化構(gòu)建神器
    2021-07-07
  • Mybatis報錯日志BindingException的解決

    Mybatis報錯日志BindingException的解決

    本文主要介紹了Mybatis報錯日志BindingException的解決,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • 簡述springboot及springboot cloud環(huán)境搭建

    簡述springboot及springboot cloud環(huán)境搭建

    這篇文章主要介紹了簡述springboot及springboot cloud環(huán)境搭建的方法,包括spring boot 基礎(chǔ)應(yīng)用環(huán)境搭建,需要的朋友可以參考下
    2017-07-07
  • 使用idea搭建一個spring mvc項目的圖文教程

    使用idea搭建一個spring mvc項目的圖文教程

    這篇文章主要介紹了使用idea直接創(chuàng)建一個spring mvc項目的圖文教程,本文通過圖文并茂的方式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-03-03
  • 你知道Java的這些騷操作嗎?

    你知道Java的這些騷操作嗎?

    今天在看python相關(guān)的東西,看到各種騷操作,回頭想了下Java有沒有什么騷操作,整理下面幾種,一起看一下吧,需要的朋友可以參考下
    2021-05-05
  • Clojure?與Java對比少數(shù)據(jù)結(jié)構(gòu)多函數(shù)勝過多個單獨類的優(yōu)點

    Clojure?與Java對比少數(shù)據(jù)結(jié)構(gòu)多函數(shù)勝過多個單獨類的優(yōu)點

    這篇文章主要介紹了Clojure?與Java對比少數(shù)據(jù)結(jié)構(gòu)多函數(shù)勝過多個單獨類的優(yōu)點,在Clojure中,我們一次又一次地使用相同的數(shù)據(jù)結(jié)構(gòu),并在其上運行許多函,更多相關(guān)介紹需要的朋友可以參考一下下面文章內(nèi)容
    2022-06-06
  • mybatis-plus雪花算法生成Id使用詳解

    mybatis-plus雪花算法生成Id使用詳解

    本文主要介紹了mybatis-plus雪花算法生成Id使用詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • java定義數(shù)組的三種類型總結(jié)

    java定義數(shù)組的三種類型總結(jié)

    下面小編就為大家?guī)硪黄猨ava定義數(shù)組的三種類型總結(jié)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-10-10
  • java sftp下載文件報錯Caused by:com.jcraft.jsch.JSchException:session is down問題

    java sftp下載文件報錯Caused by:com.jcraft.jsch.JSchExcep

    文章講述了作者在日常工作中遇到的JSch連接問題,經(jīng)過分析發(fā)現(xiàn)是由于連接泄露導(dǎo)致的,作者提出了解決方案,并給出了使用建議:1.在finally代碼塊中關(guān)閉連接;2.在真正使用階段再創(chuàng)建連接,避免創(chuàng)建后不使用又忘記關(guān)閉連接
    2024-11-11
  • Spring?Boot整合持久層之JdbcTemplate多數(shù)據(jù)源

    Spring?Boot整合持久層之JdbcTemplate多數(shù)據(jù)源

    持久層是JavaEE中訪問數(shù)據(jù)庫的核心操作,SpringBoot中對常見的持久層框架都提供了自動化配置,例如JdbcTemplate、JPA 等,MyBatis 的自動化配置則是MyBatis官方提供的。接下來分別向讀者介紹Spring Boot整合這持久層技術(shù)中的整合JdbcTemplate
    2022-08-08

最新評論

兴城市| 平塘县| 江阴市| 邳州市| 玛曲县| 南漳县| 大兴区| 新蔡县| 察哈| 南康市| 温宿县| 社旗县| 巴马| 灵丘县| 红桥区| 鲜城| 桦南县| 额敏县| 林州市| 石渠县| 西林县| 广元市| 布拖县| 长沙县| 临沂市| 托里县| 张家港市| 南城县| 青阳县| 宿松县| 丹东市| 定边县| 衡阳市| 宽甸| 江阴市| 毕节市| 东方市| 繁昌县| 游戏| 东港市| 汤阴县|