前言
在當今科技高速發展的時代,計算機的高速發展早已超越“摩爾定律”。在這個計算機相對廉價的時代,作為開發者操作的機器早已不是單核處理器了,早已進入多核時代,業務早已進入並行執行;開發高併發的程式所要掌握的技能也不再是使用沒有效率的鎖,所幸jdk1.5提供在多執行緒情況下無鎖的進行原子操作,也就是這篇文章將要寫的內容。
什麼是“原子變數類”?
自JDK1.5開始提供了java.util.concurrent.atomic包,方便程式設計師在多執行緒環境下,無鎖的進行原子操作。原子變數的底層使用了處理器提供的原子指令,但是不同的CPU架構可能提供的原子指令不一樣,也有可能需要某種形式的內部鎖,所以該方法不能絕對保證執行緒不被阻塞。- 總的來說就是提供非阻塞的執行緒安全程式設計
原子變數類的簡單用法
在介紹用法前先了解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)常用方法摘要
返回型別 | 方法 | 描述 |
---|---|---|
boolean | compareAndSet(boolean expect, boolean update) | 如果當前值 == 預期值,則以原子方式將該值設定為給定的更新值 |
boolean | get() | 返回當前值。 |
void | set(boolean newValue) | 無條件地設定為給定值。 |
boolean | weakCompareAndSet(boolean expect, boolean update) | 如果當前值 == 預期值,則以原子方式將該值設定為給定的更新值。 |
這裡介紹只列出常用的方法,實際中據原子類不同方法略有變化。如需瞭解更多的方法請檢視“線上文件-jdk-z”
(3)簡單使用示例
示例一:原子更新基本型別類--生成序列號
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 操作,這種方法仍然會比由於鎖爭用造成的重新排程快得多。