CAS你必須知道的幾點


概述

CAS(Compare-and-Swap),即比較並替換,是一種實現並發算法時常用到的技術,Java並發包中的很多類都使用了CAS技術。CAS也是現在面試經常問的問題,本文將深入的介紹CAS的原理。

 

案例

 

介紹CAS之前,我們先來看一個例子。

 

/**
 * @author dajun
 * @date 2019/7/6
 */
public class VolatileTest {
    
    public static volatile int race = 0;
 
    private static final int THREADS_COUNT = 20;
    
    public static void increase() {
        race++;
    }
 
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[THREADS_COUNT];
        for (int i = 0; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
 
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println(race);
    }
}

這個例子有些網友反饋會進入死循環,我后面也發現了,在IDEA的RUN模式下確實會陷入死循環,通過 Thread.currentThread().getThreadGroup().list(); 代碼可以打印出當前的線程情況如下:

    java.lang.ThreadGroup[name=main,maxpri=10]
        Thread[main,5,main]
        Thread[Monitor Ctrl-Break,5,main]

可以看到,除了Main方法線程后,還有一個Monitor Ctrl-Break線程,這個線程是IDEA用來監控Ctrl-Break中斷信號的線程。

解決死循環的辦法:如果是IDEA,可以使用DEBUG模式運行就可以,或者使用下面這段代碼。

import java.util.concurrent.CountDownLatch;
 
/**
 * @author dajun
 * @date 2019/7/6
 */
public class VolatileTest {
    
    public static volatile int race = 0;
 
    private static final int THREADS_COUNT = 20;
 
    private static CountDownLatch countDownLatch = new CountDownLatch(THREADS_COUNT);
 
    public static void increase() {
        race++;
    }
 
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[THREADS_COUNT];
        for (int i = 0; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        increase();
                    }
                    countDownLatch.countDown();
                }
            });
            threads[i].start();
        }
        countDownLatch.await();
        System.out.println(race);
    }
}

CAS是什么?

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

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

源碼分析

上面源碼分析時,提到最后調用了compareAndSwapInt方法,接着繼續深入探討該方法,該方法在Unsafe中對應的源碼如下

 

 

CAS的缺點:

CAS雖然很高效的解決了原子操作問題,但是CAS仍然存在三大問題。

  1. 循環時間長開銷很大。
  2. 只能保證一個變量的原子操作。
  3. ABA問題。

 

循環時間長開銷很大:

CAS 通常是配合無限循環一起使用的,我們可以看到 getAndAddInt 方法執行時,如果 CAS 失敗,會一直進行嘗試。如果 CAS 長時間一直不成功,可能會給 CPU 帶來很大的開銷。

 

只能保證一個變量的原子操作:

當對一個變量執行操作時,我們可以使用循環 CAS 的方式來保證原子操作,但是對多個變量操作時,CAS 目前無法直接保證操作的原子性。但是我們可以通過以下兩種辦法來解決:1)使用互斥鎖來保證原子性;2)將多個變量封裝成對象,通過 AtomicReference 來保證原子性

 

什么是ABA問題?ABA問題怎么解決?

如果內存地址V初次讀取的值是A,並且在准備賦值的時候檢查到它的值仍然為A,那我們就能說它的值沒有被其他線程改變過了嗎?

如果在這段期間它的值曾經被改成了B,后來又被改回為A,那CAS操作就會誤認為它從來沒有被改變過。這個漏洞稱為CAS操作的“ABA”問題。Java並發包為了解決這個問題,提供了一個帶有標記的原子引用類“AtomicStampedReference”,它可以通過控制變量值的版本來保證CAS的正確性。因此,在使用CAS前要考慮清楚“ABA”問題是否會影響程序並發的正確性,如果需要解決ABA問題,改用傳統的互斥同步可能會比原子類更高效。

 

 


免責聲明!

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



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