1. 程式人生 > >Java高並發-無鎖

Java高並發-無鎖

pda 增加 元素 ref tor help 沒有 底層 可能

一、無鎖類的原理

1.1 CAS

CAS算法的過程是這樣:它包含3個參數CAS(V,E,N)。V表示要更新的變量,E表示預期值,N表示新值。僅當V值等於E值時,才會將V的值設為N,如果V值和E值不同,則說明已經有其他線程做了更新,則當前線程什麽都不做。最後,CAS返回當前V的真實值 。CAS操作是抱著樂觀的態度進行的,它總是認為自己可以成功完成操作。當多個線程同時使用CAS操作一個變量時,只有一個會勝出,並成功更新,其余均會失敗。失敗的線程不會被掛起,僅是被告知失敗,並且允許再次嘗試,當然也允許失敗的線程放棄操作。基於這樣的原理,CAS操作即使沒有鎖,也可以發現其他線程對當前線程的幹擾,並進行恰當的處理。

1.2 CPU指令

技術分享圖片

二、無鎖類的使用

2.1 AtomicInteger

概述

java.util.concurrent.atomic
public class AtomicInteger extends Number implements java.io.Serializable

主要接口

public final int get() // 獲取當前值
public final void set(int newValue) // 設置當前值
public final int getAndSet(int newValue) // 設置新值,並返回舊值
public final boolean compareAndSet
(int expect, int u) // 如果當前值為expect,則設置為u public final int getAndIncrement() // 當前值加1,返回舊值,類似於i++ public final int getAndDecrement() // 當前值減1,返回舊值,類似於i-- public final int getAndAdd(int delta) // 當前值增加delta,返回舊值 public final int incrementAndGet() // 當前值加1,返回新值,類似於++i public final int decrementAndGet() // 當前值減1,返回新值,類似於--i
public final int addAndGet(int delta) // 當前值增加delta,返回新值

主要接口的實現

// 內部定義了一個value,AtomicInteger只是對它的封裝
private volatile int value;

compareAndSet

valueOffset是偏移量

技術分享圖片

技術分享圖片

舉例

技術分享圖片

2.2 Unsafe

概述

非安全的操作,比如:根據偏移量設置值
park()  把線程停下來
底層的CAS操作
非公開API,在不同版本的JDK中,可能有較大差異

主要接口

// 獲得給定對象偏移量上的int值
public native int getInt(java.lang.Object arg0, long arg1);
// 設置給定對象像偏移量上的int值
public native void putInt(java.lang.Object arg0, long arg1, int arg2);
// 獲得字段在對象中的偏移量
public native long objectFieldOffset(Field f);
// 設置給定對象的int值,使用volatile語義
public native void putIntVolatile(Object o, long offset, int x);
// 獲得給定對象的int值,使用volatile語義
public native void getIntVolatile(Object o, long offset);
// 和putIntVolatile()一樣,但是它要求被操作字段就是volatile類型的
public native void putOrderedInt(Object o, long offset, int x);

2.3 AtomicReference

概述

對引用進行修改
是一個模板類,抽象化了數據類型

主要接口

get()
set(V)
compareAndSet()
getAndSet(V)

舉例

技術分享圖片

2.4 AtomicStampedReference

概述

解決ABA問題
一個變量初始值為A
線程1讀取變量            00:00
線程2讀取變量            00:03
線程2將變量修改為B       00:05
線程3讀取變量            00:06
線程3將變量修改為A       00:08
線程1根據變量做計算       00:10
線程1將變量修改為C       00:12  定稿前變量值是A,其實該變量已經經歷了ABA的變化 

主要接口

// 比較設置 參數依次為:期望值 寫入新值 期望時間戳 新時間戳
public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp)
// 獲得當前對象引用
public V getReference()
// 獲得當前時間戳
public int getStamp()
// 設置當前對象引用和時間戳
public void set(V newReference, int newStamp)

舉例

public class AtomicStampedReferenceDemo {
    static AtomicStampedReference<Integer> money = new AtomicStampedReference<Integer>(19, 0);

    public static void main(String[] args) {
        // 模擬3個充錢線程,余額小余20時充錢且只充一次
        for (int i = 0; i < 3; i++) {
            final int timestamp = money.getStamp();
            new Thread() {
                public void run() {
                    while (true) {
                        Integer m = money.getReference();
                        if (m < 20) {
                            if (money.compareAndSet(m, m + 20, timestamp, timestamp + 1)) {
                                System.out.println("余額小於20元,充值成功,余額:" + money.getReference());
                            }
                        } else {
                            // 余額大於20,無需充值
                            break;
                        }
                    }
                }
            }.start();
        }
        // 模擬一個消費線程
        new Thread() {
            public void run() {
                // 消費10次,余額大於10元時才能消費
                for (int i = 0; i < 10; i++) {
                    while (true) {
                        int timestamp = money.getStamp();
                        Integer m = money.getReference();
                        if (m > 10) {
                            if (money.compareAndSet(m, m - 10, timestamp, timestamp + 1)) {
                                System.out.println("成功消費10元,余額:" + money.getReference());
                                break;
                            }
                        } else {
                            System.out.println("沒有足夠的余額");
                            break;
                        }
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                    }
                }
            }
        }.start();
    }
}

技術分享圖片

2.5 AtomicIntegerArray

概述

支持無鎖的數組

主要接口

// 獲得數組第i個下標的元素
public final int get(int i)
// 獲得數組的長度
public final int length()
// 將數組第i個下標設置為newValue,並返回舊的值
public final int getAndSet(int i, int newValue)
// 進行CAS操作,如果第i個下標的元素等於expect,則設置為update,設置成功返回true
public final boolean compareAndSet(int i, int expect, int update)
// 將第i個下標的元素加1
public final int getAndIncrement(int i)
// 將第i個下標的元素減1
public final int getAndDecrement(int i)
// 將第i個下標的元素增加delta(delta可以是負數)
public final int getAndAdd(int i, int delta)

舉例

技術分享圖片

2.6 AtomicIntegerFieldUpdater

概述

讓普通變量也享受原子操作

主要接口

// 工廠方法
AtomicIntegerFieldUpdater.newUpdater()
incrementAndGet()

說明

1. Updater只能修改它可見範圍內的變量。因為Updater使用反射得到這個變量。如果變量不可見,就會出錯。比如如果score聲明為private,就是不可行的。
2. 為了確保變量被正確讀取,它必須是volatile類型的。如果我們原有代碼中未聲明這個類型,那麽簡單聲明一個就行,這不會引起什麽問題。
3. 由於CAS操作會通過對象實例中的偏移量直接進行賦值,因此它不支持static字段(Unsafe.objectFieldOffset()不支持靜態變量)

舉例

技術分享圖片

三、無鎖算法

3.1 Vector實現

add方法

技術分享圖片

說明

數組實現
modCount++ 記錄被修改的次數
ensureCapacityHelper(elementCount+1) 做容量檢查

擴容

技術分享圖片

3.2 無鎖的Vector實現

Java高並發-無鎖