Java多線程_CAS算法和ABA問題


CAS算法概述
CAS是英文單詞CompareAndSwap的縮寫,中文意思是:比較並替換。CAS需要有3個操作數:內存地址V,舊的預期值A,即將要更新的目標值B。

CAS指令執行時,當且僅當內存地址V的值與預期值A相等時,將內存地址V的值修改為B,否則就什么都不做。整個比較並替換的操作是一個原子操作。

 

注:t1,t2線程是同時更新同一變量56的值

因為t1和t2線程都同時去訪問同一變量56,所以他們會把住內存的值完全拷貝一份到自己的工作內存空間,所以t1和t2線程值都為56

假設t1和t2在線程競爭中線程t1能去更新變量值改為57,而其他線程都失敗。(失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次發起嘗試)。T1線程去更新變量值改為57,然后寫到內存中。此時對於t2來說,內存值改為57,與預期值56不一致,就操作失敗了(想改的值不再是原來的值)。

CAS算法的開銷主要來自Cache Miss,一旦導致CAS一直更新失敗的話,它的性能是有可能壞於加鎖的方式的。

 

ABA問題概述

上面我我們說了CAS算法,CAS實現的過程是先取出內存中某時刻的數據,在下一時刻比較並替換,那么在這個時間差會導致數據的變化,此時就會導致出現“ABA”問題。關於“ABA”問題,我們假設如下事件序列:

線程 1 從內存位置V中取出A。
線程 2 從位置V中取出A。
線程 2 進行了一些操作,將B寫入位置V。
線程 2 將A再次寫入位置V。
線程 1 進行CAS操作,發現位置V中仍然是A,操作成功。
盡管線程 1 的CAS操作成功,但不代表這個過程沒有問題——對於線程 1 ,線程 2 的修改已經丟失。

我們形象地畫一個圖來打個比方:

解決方法
我們在AtomicReference的使用中就遇到了這樣的ABA問題,name怎么解決的呢?我們使用AtomicStampedReference就能很好的解決這個問題了,首先,我們先看一下這一段代碼(實現自動充值,當少於20元時,充值20元,再進行消費,每次消費10元):

import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicReferenceDemo {
    static AtomicStampedReference<Integer> money = new AtomicStampedReference<Integer>(19, 1);
    static Object obj = new Object();

    public static void main(String[] args) {
        Add add = new Add();
        Thread thread1 = new Thread(add);
        thread1.start();
        Reduce reduce = new Reduce();
        Thread thread2 = new Thread(reduce);
        thread2.start();
    }

    static class Add implements Runnable {

        @Override
        public void run() {while (true) {
                    Integer m = money.getReference();
                    synchronized (obj) {                   // 1處
                        if (m < 20) {
                            if (money.compareAndSet(m, m + 20, money.getStamp(), money.getStamp() + 1)) {   //2處  
                                System.out.println("充值成功,余額:" + money.getReference() + "元");  
                                break;
                            }
                        } else {
                            break;
                        }
                    }
                }
            }
        }

    static class Reduce implements Runnable {

        @Override
        public void run() {
            while (true) {
                while (true) {
                    Integer m = money.getReference();
                    synchronized (obj) {                   //3

                        if (m > 10) {
                            if (money.compareAndSet(m, m - 10, money.getStamp(), money.getStamp() + 1)) {     //4處
                                System.out.println("成功消費10元,余額:" + money.getReference() + "元");
                                break;
                            }
                        } else {
                            break;
                        }
                    }
                }
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

我們在1處2處加上鎖之后就OK了!這是結果:

這樣問題就很好的解決了,不過特別注意的是在1處和3處需要加鎖,因為2處和4處的if條件是一個原子操作,大家都知道,java是搶占式的,線程可能在這個原子操作執行結束后被另一個線程所搶占,這樣就是導致打印的時候的值不准確。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM