天天看點

Java并發程式設計:CAS1. CAS算法2. 線程安全的原子操作類

1. CAS算法

  • CAS 全稱 Compare And Swap(比較與交換),是一種無鎖算法。在不使用鎖(沒有線程被阻塞)的情況下實作多線程之間的變量同步。CAS是一種硬體對并發的支援,針對多處理器操作而設計的處理器中的一種特殊指令,用于管理對共享資料的并發通路。java.util.concurrent.atomic包中的原子類就是通過CAS來實作了樂觀鎖。
  • CAS算法涉及到三個操作數:
    • 需要讀寫的記憶體值 V
    • 進行比較的值 A(A本質上指的是“舊值”)
    • 要寫入的新值 B
  • 當且僅當 V 的值等于 A 時,CAS通過原子方式用新值B來更新V的值(“比較+更新”整體是一個原子操作),否則不會執行任何操作。一般情況下,“更新”是一個不斷重試的操作。
  • 有沒有可能在判斷了 V == A 之後,正準備更新它的新值的時候,被其它線程更改了 V 的值呢?不會的,因為CAS是一種原子操作,它是一種系統原語,是一條CPU的原子指令,從CPU層面保證它的原子性。當多個線程同時使用CAS操作一個變量時,隻有一個會勝出,并成功更新,其餘均會失敗,但失敗的線程并不會被挂起,僅是被告知失敗,并且允許再次嘗試,當然也允許失敗的線程放棄操作。
Java 實作 CAS 的原理:Unsafe 類
  • 在 Java 中,如果一個方法是 native 的,那 Java 就不負責具體實作它,而是交給底層的 JVM 使用 C 或者 C++ 去實作。在 Java 中,有一個 Unsafe 類,它在 sun.misc 包中,它裡面是一些 native 方法,其中就有幾個關于 CAS 的方法。Unsafe 類中對 CAS 的實作是 C++ 寫的,它的具體實作和作業系統、CPU 都有關系。
CAS 實作原子操作的三大問題:
  • ABA問題。CAS 需要在操作值的時候檢查記憶體值是否發生變化,沒有發生變化才會更新記憶體值。但是如果記憶體值原來是 A,後來變成了 B,然後又變成了 A,那麼 CAS 進行檢查時會發現值沒有發生變化,但是實際上是有變化的。ABA 問題的解決思路就是在變量前面添加版本号,每次變量更新的時候都把版本号加一,這樣變化過程就從“A-B-A”變成了“1A-2B-3A”。從 JDK 1.5 開始,JDK 的 atomic 包裡提供了一個類 AtomicStampedReference 類來解決 ABA 問題,具體操作封裝在 compareAndSet() 中。compareAndSet() 首先檢查目前引用和目前标志與預期引用和預期标志是否相等,如果都相等,則以原子方式将引用值和标志的值設定為給定的更新值。
  • 循環時間長開銷大。CAS 多與自旋結合。自旋 CAS 操作如果長時間不成功,會導緻其一直自旋,給 CPU 帶來非常大的開銷。解決思路是讓 JVM 支援處理器提供的 pause 指令,pause 指令能讓自旋失敗時 cpu 睡眠一小段時間再繼續自旋。
  • 隻能保證一個共享變量的原子操作。對一個共享變量執行操作時,CAS 能夠保證原子操作,但是對多個共享變量執行操作時,CAS 是無法保證操作的原子性的。JDK 從 1.5 開始提供了 AtomicReference 類來保證引用對象之間的原子性,可以把多個變量放在一個對象裡來進行 CAS 操作。或者使用鎖來解決這一問題,因為鎖内的臨界區代碼可以保證隻有目前線程能操作。

2. 線程安全的原子操作類

  • java.util.concurrent.atomic(Java并發原子包)包裡的類基本都是使用 Unsafe 實作的包裝類。

2.1 原子更新基本類型

  • AtomicInteger:原子更新整型
  • AtomicLong:原子更新長整型
  • AtomicBoolean:原子更新布爾類型
package com.java.atomic;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest {
    //AtomicInteger類中聲明了value屬性:private volatile int value;
    //new AtomicInteger(10); 等價于 value = 10;
    static AtomicInteger ai = new AtomicInteger(10);

    public static void main(String[] args) {

        new Thread(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(100); //該線程先睡100毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("incrementAndGet : " + ai.incrementAndGet());
        }, "A").start();


        new Thread(() -> {
            System.out.println("get : " + ai.get());
            System.out.println("getAndIncrement : " + ai.getAndIncrement());
        }, "B").start();
    }
    /**
     * 執行結果:
     * get : 10
     * getAndIncrement : 10
     * incrementAndGet : 12
     */
}
           
getAndIncrement()方法實作原子操作的原理:
  • 第一步先取出AtomicInteger裡存儲的數值;
  • 第二步對取出來的數值進行+1操作;
  • 第三步調用compareAndSet方法進行原子更新操作,該方法先檢查目前AtomicInteger裡存儲的數值是否等于current,等于意味着AtomicInteger裡存儲的數值沒有被其他線程修改過,此時将AtomicInteger裡存儲的數值更新成next;如果不等,compareAndSet會傳回false,程式會再次進入for循環重新進行compareAndSet操作。
public final int getAndIncrement() {
        for(;;) {
        	int current = get();
        	int next = current + 1;
        	if(compareAndSet(current,next)) {
        		return current;
        	}
        }
    }
    
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
           
Unsafe隻提供了3種CAS方法,檢視AtomicBoolean源碼可以看到它是先把Boolean轉換成整型,再使用compareAndSwapInt進行CAS,是以原子更新char、float、double變量也可使用類似的思路。
//Unsafe類的部分源碼:
	public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

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

    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
           

2.2 原子更新數組

  • AtomicIntegerArray:原子更新整型數組裡的元素
  • AtomicLongArray:原子更新長整型數組裡的元素
  • AtomicReferenceArray:原子更新引用類型數組裡的元素
package com.java.atomic;

import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicIntegerArrayTest {
    static int[] array = new int[]{1, 2, 3};
    //數組array通過構造器傳入,然後AtomicIntegerArray會将目前數組複制一份,
    //是以當AtomicIntegerArray對内部的數組元素進行修改時,不會影響傳入的數組。
    static AtomicIntegerArray aia = new AtomicIntegerArray(array);

    public static void main(String[] args) {
        System.out.println("索引1處的元素 = " + aia.get(1));
        System.out.println("索引1處的元素 = " + aia.getAndSet(1, 10));
        System.out.println("索引1處的元素 = " + aia.get(1));
        System.out.println("array[1] = " + array[1]);
    }
    /**
     * 執行結果:
     * 索引1處的元素 = 2
     * 索引1處的元素 = 2
     * 索引1處的元素 = 10
     * array[1] = 2
     */
}
           

2.3 原子更新引用

原子更新基本類型的AtomicInteger,隻能更新一個變量,如果要原子更新多個變量,就需要使用這個原子更新引用類型提供的類。
  • AtomicReference:原子更新引用類型
  • AtomicReferenceFieldUpdater:原子更新引用類型裡的字段
  • AtomicMarkableReference:原子更新帶有标記位的引用類型(可以原子更新一個布爾類型的标記位和引用類型)
package com.java.atomic;

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceTest {
    static AtomicReference<User> ar = new AtomicReference<User>();

    public static void main(String[] args) {
        User user = new User("AA", 18);
        ar.set(user);
        User updateUser = new User("BB", 81);
        boolean flag = ar.compareAndSet(user, updateUser);
        System.out.println(flag); //true
        System.out.println(ar.get().getName()); //BB
        System.out.println(ar.get().getAge()); //81
    }

    static class User {
        private String name;
        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }
    }
}
           

2.4 原子更新屬性 (字段)

如果需要原子的更新某個類裡的某個字段時,需要使用原子更新字段類。
  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
  • AtomicLongFieldUpdater:原子更新長整型的字段的更新器
  • AtomicStampedReference:原子更新帶有版本号的引用類型(該類将整數值與引用關聯起來,可用于原子的更新資料和資料的版本号,可以解決使用CAS進行原子更新時可能出現的ABA問題)
package com.java.atomic;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterTest {
    //建立原子更新器,并設定需要更新的對象所屬的類和對象的屬性
    //public abstract class AtomicIntegerFieldUpdater<T> { //...... }
    //AtomicIntegerFieldUpdater是抽象類,需要使用其靜态方法newUpdater建立一個實作類對象
    static AtomicIntegerFieldUpdater<User> aifu = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");

    public static void main(String[] args) {
        User user = new User("XX", 24);
        System.out.println(aifu.getAndIncrement(user)); //24
        System.out.println(aifu.get(user)); //25
    }

    static class User {
        private String name;
        //被更新的字段必須使用 public volatile 修飾
        public volatile int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }
    }
}