1. 程式人生 > >演算法:CAS演算法。

演算法:CAS演算法。

       對於併發控制而言,我們平時用的鎖(synchronized,Lock)是一種悲觀的策略。它總是假設每一次臨界區操作會產生衝突,因此,必須對每次操作都小心翼翼。如果多個執行緒同時訪問臨界區資源,就寧可犧牲效能讓執行緒進行等待,所以鎖會阻塞執行緒執行。

  與之相對的有一種樂觀的策略,它會假設對資源的訪問是沒有衝突的。既然沒有衝突也就無需等待了,所有的執行緒都在不停頓的狀態下持續執行。那如果遇到問題了無鎖的策略使用一種叫做比較交換(CAS Compare And Swap)來鑑別執行緒衝突,一旦檢測到衝突產生,就重試當前操作直到沒有衝突。CAS演算法是非阻塞的,它對死鎖問題天生免疫,而且它比基於鎖的方式擁有更優越的效能。

  CAS演算法的過程是這樣:它包含三個引數 CAS(V,E,N)。V表示要更新的變數,E表示預期的值,N表示新值。僅當V值等於E值時,才會將V的值設定成N,否則什麼都不做。最後CAS返回當前V的值。CAS演算法需要你額外給出一個期望值,也就是你認為現在變數應該是什麼樣子,如果變數不是你想象的那樣,那說明已經被別人修改過。你就重新讀取,再次嘗試修改即可。

  JDK併發包有一個atomic包,裡面實現了一些直接使用CAS操作的執行緒安全的型別。其中最常用的一個類應該就是AtomicInteger。我們以此為例來研究一下沒有鎖的情況下如何做到執行緒安全。

private volatile int value;

  這是AtomicInteger類的核心欄位,代表當前實際取值,藉助volatile保證執行緒間資料的可見性。

  獲取內部資料的方法:

public final int get() {
    return value;
}

  我們關注一下incrementAndGet()的內部實現  

public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

  程式碼第二行使用了一個死迴圈,原因是:CAS的操作未必都是成功的,因此對於不成功的情況,我們就需要進行不斷的嘗試。第三行取得當前值,接著+1得到新值next。這裡我們使用CAS必需的兩個引數:期望值以及新值。使用compareAndSet()將新值next寫入。成功的條件是在寫入的時刻,當前的值應該要等於剛剛取到的current。如果不是這樣則說明AtomicInteger的值在第3行到第5行之間被其他執行緒修改過了。當前看到的狀態是一個過期的狀態,因此返回失敗,需要進行下一次重試,直到成功為止。

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

  整體的過程就是這樣子的,利用CPU的CAS指令,同時藉助JNI來完成Java的非阻塞演算法。其它原子操作都是利用類似的特性完成的。大概的邏輯應該是這樣:

if (this == expect) {
    this = update
    return true;
} else {
    return false;
} 

  CAS雖然能高效的解決原子問題,但是CAS也會帶來1個經典問題即ABA問題:

  因為CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。

  ABA問題的解決思路就是使用版本號。在變數前面追加上版本號,每次變數更新的時候把版本號加一,那麼A-B-A 就會變成1A-2B-3A。

  從Java1.5開始JDK的atomic包裡提供了一個類AtomicStampedReference來解決ABA問題。這個類在內部不僅維護了物件值,還維護了一個時間戳(可以是任意的一個整數來表示狀態值)。當設定物件值時,物件值和狀態值都必須滿足期望值才會寫入成功。因此即使物件被反覆讀寫,寫會原值,只要狀態值發生變化,就能防止不恰當的寫入。  

/**
* @param expectedReference 期望值
* @param newReference 寫入新值
* @param expectedStamp 期望狀態值
* @param newStamp 新狀態值
* @return true if successful
*/
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) {
    Pair<V> current = pair;
    return    expectedReference == current.reference &&
        expectedStamp == current.stamp &&
        ((newReference == current.reference &&
        newStamp == current.stamp) ||
        casPair(current, Pair.of(newReference, newStamp)));
}

相關推薦

演算法CAS演算法

       對於併發控制而言,我們平時用的鎖(synchronized,Lock)是一種悲觀的策略。它總是假設每一次臨界區操作會產生衝突,因此,必須對每次操作都小心翼翼。如果多個執行緒同時訪問臨界區資源,就寧可犧牲效能讓執行緒進行等待,所以鎖會阻塞執行緒執行。   與之相

演算法設計演算法以找到最大利潤您可以根據需要完成儘可能多的交易(即,多次買入並賣出一股股票)

假設您有一個數組,其中第i個元素是第i天給定股票的價格。 設計演算法以找到最大利潤。您可以根據需要完成儘可能多的交易(即,多次買入並賣出一股股票)。 注意:您不能同時進行多筆交易(即,您必須在再次購買之前賣出股票)。 例1: 輸入: [7,1,5,3,6,4] 輸出: 7 說明:在第2天

前端演算法設計演算法以找到最大利潤您可以根據需要完成儘可能多的交易(即,多次買入並賣出一股股票)

假設您有一個數組,其中第i個元素是第i天給定股票的價格。 設計演算法以找到最大利潤。您可以根據需要完成儘可能多的交易(即,多次買入並賣出一股股票)。 注意:您不能同時進行多筆交易(即,您必須在再次購買之前賣出股票)。 例1: 輸入: [7,1,5,3,6,4]

圖形演算法直線演算法

圖形演算法:直線演算法 標籤(空格分隔): 演算法 版本:3 作者:陳小默 宣告:禁止商用,禁止轉載 1 2 3 釋出於:作業部落、CSDN部落格 場景中的直線由其兩端點的座標位置來定義。要在光柵監視器中顯示一條線段,圖形系統必須先將兩端點投影到整數螢幕座標,並確定離兩端點間的直線路徑最近的

MIP啟發式演算法遺傳演算法 (Genetic algorithm)

*本文主要記錄和分享學習到的知識,算不上原創 *參考文獻見連結 本文主要講述啟發式演算法中的遺傳演算法。遺傳演算法也是以local search為核心框架,但在表現形式上和hill climbing, tabu search, Variable neighborhood search等以一個初始解出發的演

啟發式優化演算法退火演算法

一. 爬山演算法 ( Hill Climbing ) 介紹模擬退火前,先介紹爬山演算法。爬山演算法是一種簡單的貪心搜尋演算法,該演算法每次從當前解的臨近解空間中選擇一個最優解作為當前解,直到達到一個區域性最優解。 爬山演算法實現很簡單,其主要缺點是會陷入區域性最優解,而不一定能搜尋到全域

演算法KMP 演算法實現及詳解

未完待續 、、、 求下標 K 所對應的 next 陣列對應值的具體方法為: 觀察下標 K -1 對應字串的值,令 X 為下標 K -1 對應的 next 陣列的值 若 X 為-1,則下標 K對應的 next 陣列值為 0,否則進入步驟3 觀察下標為 X 的字串的值是

分散式一致性演算法Raft 演算法

文章出處:https://www.jianshu.com/p/2a2ba021f721 Raft 演算法是可以用來替代 Paxos 演算法的分散式一致性演算法,而且 raft 演算法比 Paxos 演算法更易懂且更容易實現。本文對 raft 論文進行翻譯,希望能有助於讀

資料結構- 串的模式匹配演算法 KMP演算法

1、KMP演算法求解什麼型別問題? 字串匹配。給你兩個字串,尋找其中一個字串是否包含另一個字串,如果包含,返回包含的起始位置。 2、完整的KMP演算法 #include <bits/stdc++

No.3 磁碟排程演算法FCFS演算法、SSTF演算法、SCAN演算法、 C-SCAN演算法、 電梯演算法

首先放一張各個演算法全程即縮寫對應關係: 其中最容易搞混的就是SCAN演算法、電梯演算法、和LOOK演算法。SCAN演算法又名電梯演算法,與LOOK演算法不同 下面我們舉個例子,來看一下各個演算法的不同之處: FCFS:先來先服務 SSTF:最短尋道時間優先 SC

單源點最短路徑演算法Dijkstra演算法

背景知識 圖簡介 圖由節點和邊組成,邊有方向的圖稱為有向圖,邊沒有方向的圖稱為無向圖,最短路徑演算法裡可以把無向圖視為雙向連線的有向圖。 邊有權重的圖稱為有權圖,邊沒有權重的圖稱為無權圖,無權圖可以視為邊的權重均為1的圖。 單源點最短路徑 給定

Go語言演算法排序演算法

選擇排序 /*選擇排序,正序排列*/ func SortSliceSelected(slice []int) { for i:=0;i<len(slice)-1;i++{ for j:=i;j<len(slice);j++{ if slice[j] <

常用演算法分治演算法、動態規劃演算法、貪心演算法、回溯法、分支限界法

1、概念     回溯演算法實際上一個類似列舉的搜尋嘗試過程,主要是在搜尋嘗試過程中尋找問題的解,當發現已不滿足求解條件時,就“回溯”返回,嘗試別的路徑。    回溯法是一種選優搜尋法,按選優條件向前搜尋,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步重新選擇,這種走不通就退回再

最優化演算法BFGS演算法全稱和L-BFGS演算法全稱

在最優化演算法研究中按時間先後順序出現了許多演算法包括如下幾種,這裡介紹下他們的全稱和英文名稱: 1、最速下降法(Gradient descent) 2、牛頓法(Newton method) 3、 共軛梯度法(Conjugate Gradient) 4、擬牛頓法(Quasi-Newton),其有很多變種: (

演算法排序演算法之氣泡排序

排序算法系列目錄說明 氣泡排序(Bubble Sort) 插入排序(Insertion Sort) 希爾排序(Shell Sort) 選擇排序(Selection Sort) 快速排序(Quick Sort) 歸併排序(Merge Sort) 堆排序(Heap

linux記憶體管理演算法 夥伴演算法和slab

良好的作業系統效能部分依賴於作業系統有效管理資源的能力。在過去,堆記憶體管理器是實際的規範,但是其效能會受到記憶體碎片和記憶體回收需求的影響。現在,Linux® 核心使用了源自於 Solaris 的一種方法,但是這種方法在嵌入式系統中已經使用了很長時間了,它是將記憶體作為物件按照大小進行分配。本文將探索

演算法排序演算法之桶排序

在前幾回我們已經對氣泡排序、直接插入排序、希爾排序、選擇排序、快速排序、歸併排序、堆排序、計數排序做了說明分析(具體詳情可在公眾號歷史訊息中檢視)。本回,將對桶排序進行相關說明分析。 一、排序算法系列目錄說明 氣泡排序(Bubble Sort) 插

多源最短路徑演算法Floyd演算法

## 前言 由於本人太菜,這裡不討論Floyd的正確性。 ## 簡介 多源最短路徑,解決的是求從圖中任意兩點之間的最短路徑的問題。 ## 分析 程式碼短小精悍,主要程式碼只有四行,直接放上: ```cpp for(int k=1;kj和i->j作比較嗎,如果i->a->b->j比i->k->j更短呢? 這時

資料結構與演算法查詢演算法

查詢演算法 查詢( Search)是指從一批記錄中找出滿足指定條件的某一記錄的過程,查詢又稱為檢索。查詢演算法廣泛應用於各類應用程式中。因此,一個有效的查詢演算法往往可以大大提高程式的執行效率。在實際應用中,資料的型別千變萬化,每條資料項往往包含多個數據域。但是,在執行查詢操作時,往往只是指定一個或幾個域的值

CAS無鎖演算法ReentrantLock,synchronized(JDK 1.6),悲觀鎖/樂觀鎖

> 悲觀鎖/樂觀鎖  悲觀鎖:總是假設最壞的情況,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會阻塞直到它拿到鎖。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。再比如J