摘要:,即比較并交換,也是實(shí)現(xiàn)我們平時(shí)所說(shuō)的自旋鎖或樂(lè)觀鎖的核心操作。在多線程環(huán)境下,原子操作是保證線程安全的重要手段。如下面這個(gè)例子,個(gè)線程,每個(gè)線程都執(zhí)行次操作,我們期望的值是,但是很遺憾,結(jié)果總是小于的。
CAS(Compare and swap),即比較并交換,也是實(shí)現(xiàn)我們平時(shí)所說(shuō)的自旋鎖或樂(lè)觀鎖的核心操作。
它的實(shí)現(xiàn)很簡(jiǎn)單,就是用一個(gè)預(yù)期的值和內(nèi)存值進(jìn)行比較,如果兩個(gè)值相等,就用預(yù)期的值替換內(nèi)存值,并返回 true。否則,返回 false。
保證原子操作任何技術(shù)的出現(xiàn)都是為了解決某些特定的問(wèn)題, CAS 要解決的問(wèn)題就是保證原子操作。原子操作是什么,原子就是最小不可拆分的,原子操作就是最小不可拆分的操作,也就是說(shuō)操作一旦開(kāi)始,就不能被打斷,知道操作完成。在多線程環(huán)境下,原子操作是保證線程安全的重要手段。舉個(gè)例子來(lái)說(shuō),假設(shè)有兩個(gè)線程在工作,都想對(duì)某個(gè)值做修改,就拿自增操作來(lái)說(shuō)吧,要對(duì)一個(gè)整數(shù) i 進(jìn)行自增操作,需要基本的三個(gè)步驟:
1、讀取 i 的當(dāng)前值;
2、對(duì) i 值進(jìn)行加 1 操作;
3、將 i 值寫(xiě)回內(nèi)存;
假設(shè)兩個(gè)進(jìn)程都讀取了 i 的當(dāng)前值,假設(shè)是 0,這時(shí)候 A 線程對(duì) i 加 1 了,B 線程也 加 1,最后 i 的是 1 ,而不是 2。這就是因?yàn)樽栽霾僮鞑皇窃硬僮鳎殖傻倪@三個(gè)步驟可以被干擾。如下面這個(gè)例子,10個(gè)線程,每個(gè)線程都執(zhí)行 10000 次 i++ 操作,我們期望的值是 100,000,但是很遺憾,結(jié)果總是小于 100,000 的。
static int i = 0; public static void add(){ i++; } private static class Plus implements Runnable{ @Override public void run(){ for(int k = 0;k<10000;k++){ add(); } } } public static void main(String[] args) throws InterruptedException{ Thread[] threads = new Thread[10]; for(int i = 0;i<10;i++){ threads[i] = new Thread(new Plus()); threads[i].start(); } for(int i = 0;i<10;i++){ threads[i].join(); } System.out.println(i); }
既然這樣,那怎么辦。沒(méi)錯(cuò),也許你已經(jīng)想到了,可以加鎖或者利用 synchronized 實(shí)現(xiàn),例如,將 add() 方法修改為如下這樣:
public synchronized static void add(){ i++; }
或者,加鎖操作,例如下面使用 ReentrantLock (可重入鎖)實(shí)現(xiàn)。
private static Lock lock = new ReentrantLock(); public static void add(){ lock.lock(); i++; lock.unlock(); }CAS 實(shí)現(xiàn)自旋鎖
既然用鎖或 synchronized 關(guān)鍵字可以實(shí)現(xiàn)原子操作,那么為什么還要用 CAS 呢,因?yàn)榧渔i或使用 synchronized 關(guān)鍵字帶來(lái)的性能損耗較大,而用 CAS 可以實(shí)現(xiàn)樂(lè)觀鎖,它實(shí)際上是直接利用了 CPU 層面的指令,所以性能很高。
上面也說(shuō)了,CAS 是實(shí)現(xiàn)自旋鎖的基礎(chǔ),CAS 利用 CPU 指令保證了操作的原子性,以達(dá)到鎖的效果,至于自旋呢,看字面意思也很明白,自己旋轉(zhuǎn),翻譯成人話就是循環(huán),一般是用一個(gè)無(wú)限循環(huán)實(shí)現(xiàn)。這樣一來(lái),一個(gè)無(wú)限循環(huán)中,執(zhí)行一個(gè) CAS 操作,當(dāng)操作成功,返回 true 時(shí),循環(huán)結(jié)束;當(dāng)返回 false 時(shí),接著執(zhí)行循環(huán),繼續(xù)嘗試 CAS 操作,直到返回 true。
其實(shí) JDK 中有好多地方用到了 CAS ,尤其是 java.util.concurrent包下,比如 CountDownLatch、Semaphore、ReentrantLock 中,再比如 java.util.concurrent.atomic 包下,相信大家都用到過(guò) Atomic* ,比如 AtomicBoolean、AtomicInteger 等。
這里拿 AtomicBoolean 來(lái)舉個(gè)例子,因?yàn)樗銐蚝?jiǎn)單。
public class AtomicBoolean implements java.io.Serializable { private static final long serialVersionUID = 4654671469794556979L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicBoolean.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; public final boolean get() { return value != 0; } public final boolean compareAndSet(boolean expect, boolean update) { int e = expect ? 1 : 0; int u = update ? 1 : 0; return unsafe.compareAndSwapInt(this, valueOffset, e, u); } }
這是 AtomicBoolean 的部分代碼,我們看到這里面又幾個(gè)關(guān)鍵方法和屬性。
1、使用了 sun.misc.Unsafe 對(duì)象,這個(gè)類提供了一系列直接操作內(nèi)存對(duì)象的方法,只是在 jdk 內(nèi)部使用,不建議開(kāi)發(fā)者使用;
2、value 表示實(shí)際值,可以看到 get 方法實(shí)際是根據(jù) value 是否等于0來(lái)判斷布爾值的,這里的 value 定義為 volatile,因?yàn)?volatile 可以保證內(nèi)存可見(jiàn)性,也就是 value 值只要發(fā)生變化,其他線程是馬上可以看到變化后的值的;下一篇會(huì)講一下 volatile 可見(jiàn)性問(wèn)題,歡迎關(guān)注
3、valueOffset 是 value 值的內(nèi)存偏移量,用 unsafe.objectFieldOffset 方法獲得,用作后面的 compareAndSet 方法;
4、compareAndSet 方法,這就是實(shí)現(xiàn) CAS 的核心方法了,在使用 AtomicBoolean 的這個(gè)方法時(shí),只需要傳遞期望值和待更新的值即可,而它里面調(diào)用了 unsafe.compareAndSwapInt(this, valueOffset, e, u) 方法,它是個(gè) native 方法,用 c++ 實(shí)現(xiàn),具體的代碼就不貼了,總之是利用了 CPU 的 cmpxchg 指令完成比較并替換,當(dāng)然根據(jù)具體的系統(tǒng)版本不同,實(shí)現(xiàn)起來(lái)也有所區(qū)別,感興趣的可以自行搜一下相關(guān)文章。
使用場(chǎng)景CAS 適合簡(jiǎn)單對(duì)象的操作,比如布爾值、整型值等;
CAS 適合沖突較少的情況,如果太多線程在同時(shí)自旋,那么長(zhǎng)時(shí)間循環(huán)會(huì)導(dǎo)致 CPU 開(kāi)銷(xiāo)很大;
比如 AtomicBoolean 可以用在這樣一個(gè)場(chǎng)景下,系統(tǒng)需要根據(jù)一個(gè)布爾變量的狀態(tài)屬性來(lái)判斷是否需要執(zhí)行一些初始化操作,如果是多線程的環(huán)境下,避免多次重復(fù)執(zhí)行,可以使用 AtomicBoolean 來(lái)實(shí)現(xiàn),偽代碼如下:
private final static AtomicBoolean flag = new AtomicBoolean(); if(flag.compareAndSet(false,true)){ init(); }
比如 AtomicInteger 可以用在計(jì)數(shù)器中,多線程環(huán)境中,保證計(jì)數(shù)準(zhǔn)確。
ABA問(wèn)題CAS 存在一個(gè)問(wèn)題,就是一個(gè)值從 A 變?yōu)?B ,又從 B 變回了 A,這種情況下,CAS 會(huì)認(rèn)為值沒(méi)有發(fā)生過(guò)變化,但實(shí)際上是有變化的。對(duì)此,并發(fā)包下倒是有 AtomicStampedReference 提供了根據(jù)版本號(hào)判斷的實(shí)現(xiàn),可以解決一部分問(wèn)題。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/69321.html
摘要:公平鎖非公平鎖公平鎖公平鎖是指多個(gè)線程按照申請(qǐng)鎖的順序來(lái)獲取鎖。加鎖后,任何其他試圖再次加鎖的線程會(huì)被阻塞,直到當(dāng)前進(jìn)程解鎖。重量級(jí)鎖會(huì)讓其他申請(qǐng)的線程進(jìn)入阻塞,性能降低。 Java 中15種鎖的介紹 在讀很多并發(fā)文章中,會(huì)提及各種各樣鎖如公平鎖,樂(lè)觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內(nèi)容如下: 公平鎖 / 非公平鎖 可重入鎖 / 不可重入鎖 獨(dú)享鎖 / 共享鎖 互斥鎖 / 讀...
摘要:本文旨在對(duì)鎖相關(guān)源碼本文中的源碼來(lái)自使用場(chǎng)景進(jìn)行舉例,為讀者介紹主流鎖的知識(shí)點(diǎn),以及不同的鎖的適用場(chǎng)景。中,關(guān)鍵字和的實(shí)現(xiàn)類都是悲觀鎖。自適應(yīng)意味著自旋的時(shí)間次數(shù)不再固定,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來(lái)決定。 前言 Java提供了種類豐富的鎖,每種鎖因其特性的不同,在適當(dāng)?shù)膱?chǎng)景下能夠展現(xiàn)出非常高的效率。本文旨在對(duì)鎖相關(guān)源碼(本文中的源碼來(lái)自JDK 8)、使用場(chǎng)景...
摘要:有可能,會(huì)造成優(yōu)先級(jí)反轉(zhuǎn)或者饑餓現(xiàn)象。悲觀鎖在中的使用,就是利用各種鎖。對(duì)于而言,其是獨(dú)享鎖。偏向鎖,顧名思義,它會(huì)偏向于第一個(gè)訪問(wèn)鎖的線程,大多數(shù)情況下鎖不僅不存在多線程競(jìng)爭(zhēng),而且總是由同一線程多次獲得。 理解鎖的基礎(chǔ)知識(shí) 如果想要透徹的理解java鎖的來(lái)龍去脈,需要先了解以下基礎(chǔ)知識(shí)。 基礎(chǔ)知識(shí)之一:鎖的類型 按照其性質(zhì)分類 公平鎖/非公平鎖 公平鎖是指多個(gè)線程按照申請(qǐng)鎖的順序來(lái)獲...
摘要:并發(fā)機(jī)制與底層實(shí)現(xiàn)原理是輕量級(jí)的它在多處理器開(kāi)發(fā)中保證了共享變量的可見(jiàn)性,因?yàn)樗粫?huì)引起線程上下文的切換和調(diào)度,所以比的使用和執(zhí)行成本更底。如果線程間存在鎖競(jìng)爭(zhēng),會(huì)帶來(lái)額外的鎖撤銷(xiāo)的消耗。輕量級(jí)鎖競(jìng)爭(zhēng)的線程不會(huì)阻塞,提高了程序的響應(yīng)速度。 java并發(fā)機(jī)制與底層實(shí)現(xiàn)原理 volatile volatile是輕量級(jí)的synchronize,它在多處理器開(kāi)發(fā)中保證了共享變量的可見(jiàn)性,因?yàn)樗?..
摘要:第三天,太監(jiān)傳話欽天監(jiān)求見(jiàn)一日無(wú)事。第四天,欽天監(jiān)一日無(wú)事。然后所有的競(jìng)爭(zhēng)線程放棄自旋,逐個(gè)插入到對(duì)象里的一個(gè)隊(duì)列尾部,進(jìn)入阻塞狀態(tài)。 微信公眾號(hào):IT一刻鐘大型現(xiàn)實(shí)非嚴(yán)肅主義現(xiàn)場(chǎng)一刻鐘與你分享優(yōu)質(zhì)技術(shù)架構(gòu)與見(jiàn)聞,做一個(gè)有劇情的程序員關(guān)注可第一時(shí)間了解更多精彩內(nèi)容,定期有福利相送喲。 showImg(https://segmentfault.com/img/bVbrgsJ?w=900...
閱讀 2849·2021-11-22 11:56
閱讀 3553·2021-11-15 11:39
閱讀 898·2021-09-24 09:48
閱讀 759·2021-08-17 10:14
閱讀 1322·2019-08-30 15:55
閱讀 2753·2019-08-30 15:55
閱讀 1310·2019-08-30 15:44
閱讀 2775·2019-08-30 10:59