ABA問題的解決--CAS

CAS的三個缺點

第一個缺點:while循環會造成一直匹配不到值的循環操作,這也是CAS的一個缺點

第二個缺點:只能對一個對象進行原子操作,並不能對一塊代碼進行原子操作

第三個缺點就是接下來要講的ABA問題
ABA問題的產生:
ABA問題就是加入主存中的值爲A
(1)兩個線程都會去主存裏拿到一份值A
(2)t1線程由於執行時間短,兩秒,所以執行了又A->B->A的操作
(3)由於JMM原理,主存中也是同樣執行了這樣的操作
(4)線程t2在執行完之後看到主存中的值還是A就進行交換,但其實已經被修改過了,又改回來了
在這裏插入圖片描述
解決:
加上一個時間戳,每改變一次,自增一次,相當於版本號
使用原子引用類AtomicStampedReference

    public AtomicStampedReference(V initialRef, int initialStamp) {
        pair = Pair.of(initialRef, initialStamp);
    }

參數一還是對象,參數二則是一個初始版本號
然後每次改變主存中的值就會改變版本號,這樣就有效 避免了ABA問題的發生
解決ABA問題demo如下

package com.wsx.aba;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;

public class SloveAba {
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);

    public static void main(String[] args) {
        System.out.println("ABA問題的產生");
        new Thread(()->{
            atomicReference.compareAndSet(100,101);
            atomicReference.compareAndSet(101,100);
            System.out.println(Thread.currentThread().getName()+"\t"+atomicReference.get());
        },"t1").start();

        new Thread(()->{
            try {
                //爲了讓t1完成一次ABA操作
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicReference.compareAndSet(100,2019);

            System.out.println(Thread.currentThread().getName()+"\t"+atomicReference.get());
        },"t2").start();

        //主線程睡眠等待上限操作完成
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("ABA問題的解決");


        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            //這裏睡一秒是爲了t3線程和t4線程的起點一致,版本號都爲1
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"\t最新版本號"+atomicStampedReference.getStamp());
            //ABA帶版本號操作
            atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t最新版本號"+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t最新版本號"+atomicStampedReference.getStamp());

        },"t3").start();

        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            //這裏睡兩秒是爲了讓t3線程完成ABA操作
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //這裏的stamp是默認沒被修改的版本號,如果被其他線程修改過那麼這裏版本號就開始落後,然後修改失敗
            boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);

            System.out.println(Thread.currentThread().getName()+"\t最新版本號"+atomicStampedReference.getStamp());
            System.out.println(Thread.currentThread().getName()+"\t是否改動成功"+result);
            System.out.println(Thread.currentThread().getName()+"\t最新值"+atomicStampedReference.getReference());
        },"t4").start();
    }

}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章