Atomic包的作用:

方便程序員在多線程環境下,無鎖的進行原子操作

Atomic包核心:

Atomic包裏的類基本都是使用Unsafe實現的包裝類,核心操作是CAS原子操作

關於CAS

compare and swap,比較和替換技術,將預期值與當前變量的值比較(compare),如果相等則使用新值替換(swap)當前變量,否則不作操作;

現代CPU已廣泛支持CAS指令,如果不支持,那麼JVM將使用自旋鎖,與互斥鎖一樣,兩者都需先獲取鎖才能訪問共享資源,但互斥鎖會導致線程進入睡眠,而自旋鎖會一直循環等待直到獲取鎖;

另外,有一點需要注意的是CAS操作中的ABA問題,即將預期值與當前變量的值比較的時候,即使相等也不能保證變量沒有被修改過,因為變量可能由A變成B再變回A,解決該問題,可以給變量增加一個版本號,每次修改變量時版本號自增,比較的時候,同時比較變量的值和版本號即可

Atomic包主要提供四種原子更新方式

  • 原子方式更新基本類型
  • 原子方式更新數組
  • 原子方式更新引用
  • 原子方式更新字段

原子方式更新基本類型

以下三個類是以原子方式更新基本類型

  • AtomicBoolean:原子更新布爾類型。
  • AtomicInteger:原子更新整型。
  • AtomicLong:原子更新長整型。

以AtomicInteger為例:

package cn.com.example.concurrent.atomic;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by Jack on 2017/1/7.
 */
public class AtomicIntegerTest extends Thread {

    private AtomicInteger atomicInteger;

    public AtomicIntegerTest(AtomicInteger atomicInteger) {
        this.atomicInteger = atomicInteger;
    }

    @Override
    public void run() {
        int i = atomicInteger.incrementAndGet();
        System.out.println("generated  out number:" + i);
    }

    public static void main(String[] args) {
        AtomicInteger counter = new AtomicInteger();
        for (int i = 0; i < 10; i++) {//10個線程
            new AtomicIntegerTest(counter).start();
        }
    }
}

輸出:

generated  out number:1
generated  out number:2
generated  out number:3
generated  out number:4
generated  out number:5
generated  out number:6
generated  out number:7
generated  out number:8
generated  out number:9
generated  out number:10

注意:Atomic包提供了三種基本類型的原子更新,剩餘的Java的基本類型還有char,float和double等,其更新方式可以參考AtomicBoolean的思路來現,AtomicBoolean是把boolean轉成整型再調用compareAndSwapInt進行CAS來實現的,類似的short和byte也可以轉成整形,float和double可以利用Float.floatToIntBits,Double.doubleToLongBits轉成整形和長整形進行相應處理

原子方式更新數組

以下三個類是以原子方式更新數組

  • AtomicIntegerArray:原子更新整型數組裏的元素。
  • AtomicLongArray:原子更新長整型數組裏的元素。
  • AtomicReferenceArray:原子更新引用類型數組裏的元素

以AtomicIntegerArray為例,其方法與AtomicInteger很像,多了個數組下標索引

package cn.com.example.concurrent.atomic;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicIntegerArray;

/**
 * Created by Jack on 2017/1/7.
 */
public class AtomicIntegerArrayTest {
    private static int threadCount = 1000;
    private static CountDownLatch countDown = new CountDownLatch(threadCount);
    static int[] values = new int[10];
    static AtomicIntegerArray ai = new AtomicIntegerArray(values);

    private static class Counter implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                for (int j = 0; j < 10; j++) {//所有元素+1
                    ai.getAndIncrement(j);
                }
            }
            countDown.countDown();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[threadCount];
        for (int i = 0; i < threadCount; i++) {
            threads[i] = new Thread(new Counter());
        }
        for (int i = 0; i < threadCount; i++) {
            threads[i].start();
        }
        countDown.await();
        for (int i = 0; i < 10; i++) {
            System.out.println(ai.get(i) + " ");
        }
        System.out.println();
        for (int i = 0; i < 10; i++) {
            System.out.println(values[i] + " ");
        }
    }
}

輸出:

100000 
100000 
100000 
100000 
100000 
100000 
100000 
100000 
100000 
100000 

0 
0 
0 
0 
0 
0 
0 
0 
0 
0

需要注意的是,數組value通過構造方法傳遞進去,然後AtomicIntegerArray會將當前數組複製一份,所以當AtomicIntegerArray對內部的數組元素進行修改時,不會影響傳入的數組。

原子方式更新引用

以下三個類是以原子方式更新引用,與其它不同的是,更新引用可以更新多個變量,而不是一個變量

  • AtomicReference:原子更新引用類型。
  • AtomicReferenceFieldUpdater:原子更新引用類型裏的字段。
  • AtomicMarkableReference:原子更新帶有標記位的引用類型。

以AtomicReference為例

package cn.com.example.concurrent.atomic;

import java.util.concurrent.atomic.AtomicReference;

/**
 * Created by Jack on 2017/1/7.
 */
public class AtomicReferenceTest {
    public static void main(String[] args) {

        // 創建兩個Person對象,它們的id分別是101和102。
        Person p1 = new Person(101);
        Person p2 = new Person(102);
        // 新建AtomicReference對象,初始化它的值為p1對象
        AtomicReference ar = new AtomicReference(p1);
        // 通過CAS設置ar。如果ar的值為p1的話,則將其設置為p2。
        ar.compareAndSet(p1, p2);

        Person p3 = (Person) ar.get();
        System.out.println("p3 is " + p3);
        System.out.println("p3.equals(p1)=" + p3.equals(p1));
    }
}

class Person {
    volatile long id;

    public Person(long id) {
        this.id = id;
    }

    public String toString() {
        return "id:" + id;
    }
}

輸出:

p3 is id:102
p3.equals(p1)=false

新建AtomicReference對象ar時,將它初始化為p1。
緊接着,通過CAS函數對它進行設置。如果ar的值為p1的話,則將其設置為p2。
最後,獲取ar對應的對象,並打印結果。p3.equals(p1)的結果為false,這是因為Person並沒有覆蓋equals()方法,而是採用繼承自Object.java的equals()方法;而Object.java中的equals()實際上是調用"=="去比較兩個對象,即比較兩個對象的地址是否相等。

原子方式更新字段

以下三個類是以原子方式更新字段

  • AtomicIntegerFieldUpdater:原子更新整型字段的更新器。
  • AtomicLongFieldUpdater:原子更新長整型字段的更新器。
  • AtomicStampedReference:原子更新帶有版本號的引用類型,用於解決使用CAS進行原子更新時,可能出現的ABA問題。

以AtomicIntegerFieldUpdater為例

package cn.com.example.concurrent.atomic;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * Created by Jack on 2017/1/7.
 */
public class AtomicIntegerFieldUpdaterTest {

    // 創建原子更新器,並設置需要更新的對象類和對象的屬性
    private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "old");

    public static void main(String[] args) throws InterruptedException {
        // 設置柯南的年齡是10歲
        User conan = new User("conan", 10);
        // 柯南長了一歲,但是仍然會輸出舊的年齡
        System.out.println(a.getAndIncrement(conan));
        // 輸出柯南現在的年齡
        System.out.println(a.get(conan));
    }

    public static class User {
        private String name;
        public volatile int old;

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

        public String getName() {
            return name;
        }

        public int getOld() {
            return old;
        }
    }
}

輸出:

10
11

注意: old 需要聲明為 volatile