(基礎系列)atomic原子變量類的用法、原理和用途

前言

在當今科技高速發展的時代,計算機的高速發展早已超越「摩爾定律」。在這個計算機相對廉價的時代,做爲開發者操做的機器早已不是單核處理器了,早已進入多核時代,業務早已進入並行執行;開發高併發的程序所要掌握的技能也再也不是使用沒有效率的鎖,所幸jdk1.5提供在多線程狀況下無鎖的進行原子操做,也就是這篇文章將要寫的內容。java

什麼是「原子變量類」?

自JDK1.5開始提供了java.util.concurrent.atomic包,方便程序員在多線程環境下,無鎖的進行原子操做。原子變量的底層使用了處理器提供的原子指令,可是不一樣的CPU架構可能提供的原子指令不同,也有可能須要某種形式的內部鎖,因此該方法不能絕對保證線程不被阻塞。- 總的來講就是提供非阻塞的線程安全編程node

原子變量類的簡單用法

在介紹用法前先了解jdk軟件包 java.util.concurrent.atomic 中爲咱們提供了哪些原子類和方法: 程序員

(1)類摘要 編程

描述
AtomicBoolean 能夠用原子方式更新的 boolean 值。
AtomicInteger 能夠用原子方式更新的 int 值。
AtomicIntegerArray 能夠用原子方式更新其元素的 int 數組。
AtomicIntegerFieldUpdater 基於反射的實用工具,能夠對指定類的指定 volatile int 字段進行原子更新。
AtomicLong 能夠用原子方式更新的 long 值。
AtomicLongArray 能夠用原子方式更新其元素的 long 數組。
AtomicLongFieldUpdater 基於反射的實用工具,能夠對指定類的指定 volatile long 字段進行原子更新。
AtomicMarkableReference AtomicMarkableReference 維護帶有標記位的對象引用,能夠原子方式對其進行更新。
AtomicReference 能夠用原子方式更新的對象引用。
AtomicReferenceArray 能夠用原子方式更新其元素的對象引用數組。
AtomicReferenceFieldUpdater 基於反射的實用工具,能夠對指定類的指定 volatile 字段進行原子更新。
AtomicStampedReference AtomicStampedReference 維護帶有整數「標誌」的對象引用,能夠用原子方式對其進行更新。

(2)經常使用方法摘要api

返回類型 方法 描述
boolean compareAndSet(boolean expect, boolean update) 若是當前值 == 預期值,則以原子方式將該值設置爲給定的更新值
boolean get() 返回當前值。
void set(boolean newValue) 無條件地設置爲給定值。
boolean weakCompareAndSet(boolean expect, boolean update) 若是當前值 == 預期值,則以原子方式將該值設置爲給定的更新值。

這裏介紹只列出經常使用的方法,實際中據原子類不一樣方法略有變化。如需瞭解更多的方法請查看「在線文檔-jdk-z」數組

在線文檔-jdk-zh安全

(3)簡單使用示例 bash

示例一:原子更新基本類型類--生成序列號數據結構

public class Example1 {

    private final AtomicLong sequenceNumber = new AtomicLong(0);
    public long next() {
        //原子增量方法,執行的是i++,因此須要在獲取一次。
        sequenceNumber.getAndIncrement();
        return sequenceNumber.get();
    }

    public void radixNext(int radix){
        for (;;) {
            long i = sequenceNumber.get();
            // 該方法不必定執行成功,因此用無限循環來保證操做始終會執行成功一次。
            boolean suc = sequenceNumber.compareAndSet(i, i + radix);
            if (suc) {
                break;
            }
        }
    }


    public static void main(String[] args) {
        Example1 sequencer = new Example1();

        //生成序列號
        for (int i = 0; i < 10; i++) {
            System.out.println(sequencer.next());
        }

        //生成自定義序列號
        for (int i = 0; i < 10; i++) {
            sequencer.radixNext(3);
            System.out.println(sequencer.sequenceNumber.get());
        }


    }

}複製代碼

執行結果:多線程

1
2
3
4
5
---------------
8
11
14
17
20複製代碼

示例二:原子方式更新數組

public class Example2 {

    static AtomicIntegerArray arr = new AtomicIntegerArray(10);

    public static class AddThread implements Runnable{
        public void run(){
            for(int k=0;k<10000;k++){
                // 以原子方式將索引 i 的元素加 1。
                arr.getAndIncrement(k%arr.length());
            }

        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[]ts=new Thread[10];
        //建立10個線程
        for(int k=0;k<10;k++){
            ts[k] = new Thread(new AddThread());
        }

        //開啓10個線程
        for(int k=0;k<10;k++){
            ts[k].start();
        }

        //等待全部線程執行完成
        for(int k=0;k<10;k++){
            ts[k].join();
        }

        //打印最終執行結果
        System.out.println(arr);
    }
}複製代碼

執行結果:

[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]複製代碼

示例三:原子方式更新引用

public class Node {
    private int val;
    private volatile Node left, right;

    private static final AtomicReferenceFieldUpdater leftUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "left");
    private static AtomicReferenceFieldUpdater rightUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "right");

    boolean compareAndSetLeft(Node expect, Node update) {
        return leftUpdater.compareAndSet(this, expect, update);
    }

    public Node() {
        this.left = this.right = null;
    }

    public Node(int val) {
        this.val = val;
        this.left = this.right = null;
    }

    public Node(Node left,Node right) {
        this.left = left;
        this.right = right;
    }


    public static void main(String[] args) {
        Node node = new Node(1);
        node.left = new Node(new Node(2),new Node(3));
        node.right = new Node(new Node(4),new Node(5));
        System.out.println(JSON.toJSON(node));
        node.compareAndSetLeft(node.left,node.right);
        System.out.println(JSON.toJSON(node));
    }



    // get and set ...

}複製代碼

執行結果:

{"val":1,"left":{"val":0,"left":{"val":2},"right":{"val":3}},"right":{"val":0,"left":{"val":4},"right":{"val":5}}}
{"val":1,"left":{"val":0,"left":{"val":4},"right":{"val":5}},"right":{"val":0,"left":{"val":4},"right":{"val":5}}}複製代碼

(4)小結

原子訪問和更新的內存效果通常遵循如下可變規則中的聲明:

  • get 具備讀取 volatile 變量的內存效果。
  • set 具備寫入(分配)volatile 變量的內存效果。
    除了容許使用後續(但不是之前的)內存操做,其自身不施加帶有普通的非 volatile 寫入的從新排序約束,lazySet 具備寫入(分配)volatile 變量的內存效果。在其餘使用上下文中,當爲 null 時(爲了垃圾回收),lazySet 能夠應用不會再次訪問的引用。
  • weakCompareAndSet 以原子方式讀取和有條件地寫入變量但不 建立任何 happen-before 排序,所以不提供與除 - weakCompareAndSet 目標外任何變量之前或後續讀取或寫入操做有關的任何保證。
  • compareAndSet 和全部其餘的讀取和更新操做(如 getAndIncrement)都有讀取和寫入 volatile 變量的內存效果。

原子操做的實現原理

關鍵源碼:

// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();複製代碼

查看源碼發現Atomic包裏的類基本都是使用Unsafe實現的,Unsafe只提供瞭如下三種CAS方法。

關鍵源碼:

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);複製代碼

查看方法不難發現是被native修飾的,即被Native修飾的方法在被調用時指向的是一個非java代碼的具體實現,這個實現多是其餘語言或者操做系統。這裏是藉助C來調用CPU底層指令來實現的,具體實現原理請點擊下面的「實現原理」。

實現原理

原子對象的用途

原子變量類主要用做各類構造塊,用於實現非阻塞數據結構和相關的基礎結構類。compareAndSet方法不是鎖的常規替換方法。僅當對象的重要更新限定於單個變量時才應用它。

例:多線程高併發計數器

總結 (摘自網上)

原子變量類相對於基於鎖的版本有幾個性能優點。首先,它用硬件的原生形態代替 JVM 的鎖定代碼路徑,從而在更細的粒度層次上(獨立的內存位置)進行同步,失敗的線程也能夠當即重試,而不會被掛起後從新調度。更細的粒度下降了爭用的機會,不用從新調度就能重試的能力也下降了爭用的成本。即便有少許失敗的 CAS 操做,這種方法仍然會比因爲鎖爭用形成的從新調度快得多。

相關文章
相關標籤/搜索