自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java進(jìn)階:線程并發(fā)之深入理解CAS機(jī)制詳解

開(kāi)發(fā) 后端
獨(dú)占鎖是一種悲觀鎖,synchronized就是一種獨(dú)占鎖,會(huì)導(dǎo)致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。

[[424670]]

前言

獨(dú)占鎖是一種悲觀鎖,synchronized就是一種獨(dú)占鎖,會(huì)導(dǎo)致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖;

而另一個(gè)更加有效的鎖就是樂(lè)觀鎖。所謂樂(lè)觀鎖就是,每次不加鎖而是假設(shè)沒(méi)有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試,直到成功為止。樂(lè)觀鎖用到的機(jī)制就是CAS;

今天我們就來(lái)介紹下cas機(jī)制;

一、CAS介紹

1、什么是CAS

  • CAS,compare and swap的縮寫,中文翻譯成比較并交換。CAS指令在Intel CPU上稱為CMPXCHG指令,它的作用是將指定內(nèi)存地址的內(nèi)容與所給的某個(gè)值相比,如果相等,則將其內(nèi)容替換為指令中提供的新值,如果不相等,則更新失敗從內(nèi)存領(lǐng)域來(lái)說(shuō)這是樂(lè)觀鎖,因?yàn)樗趯?duì)共享變量更新之前會(huì)先比較當(dāng)前值是否與更新前的值一致,如果是,則更新,如果不是,則無(wú)限循環(huán)執(zhí)行(稱為自旋),直到當(dāng)前值與更新前的值一致為止,才執(zhí)行更新;
  • CAS 操作包含三個(gè)操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。如果內(nèi)存位置的值與預(yù)期原值相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值 。否則,處理器不做任何操作。無(wú)論哪種情況,它都會(huì)在 CAS 指令之前返回該 位置的值;
  • 通常將 CAS 用于同步的方式是從地址 V 讀取值 A,執(zhí)行多步計(jì)算來(lái)獲得新 值 B,然后使用 CAS 將 V 的值從 A 改為 B。如果 V 處的值尚未同時(shí)更改,則 CAS 操作成功;
  • 類似于 CAS 的指令允許算法執(zhí)行讀-修改-寫操作,而無(wú)需害怕其他線程同時(shí) 修改變量,因?yàn)槿绻渌€程修改變量,那么 CAS 會(huì)檢測(cè)它(并失敗),算法 可以對(duì)該操作重新計(jì)算;

2、那些地方采用了 CAS 機(jī)制

  • 在 java.util.concurrent.atomic 包下,一系列以 Atomic 開(kāi)頭的包裝類。例如AtomicBoolean,AtomicInteger,AtomicLong 等,它們就是典型的利用 CAS 機(jī)制實(shí)現(xiàn)的原子操作類;
  • Lock 系列類的底層實(shí)現(xiàn)以及 Java 1.6 在 synchronized 轉(zhuǎn)換為重量級(jí)鎖之前,也會(huì)采用到 CAS 機(jī)制;

3、synchronized 和 CAS 的區(qū)別

  • synchronized 采用的是 CPU 悲觀鎖機(jī)制,即線程獲得的是獨(dú)占鎖。獨(dú)占鎖就意味著 其他線程只能依靠阻塞來(lái)等待線程釋放鎖。而在 CPU 轉(zhuǎn)換線程阻塞時(shí)會(huì)引起線程上下文切換,當(dāng)有很多線程競(jìng)爭(zhēng)鎖的時(shí)候,會(huì)引起 CPU 頻繁的上下文切換導(dǎo)致效率很低。盡管 Java1.6 為 synchronized 做了優(yōu)化,增加了從偏向鎖到輕量級(jí)鎖再到重量級(jí)鎖的過(guò)度,但是在最終轉(zhuǎn)變?yōu)橹亓考?jí)鎖之后,性能仍然較低;
  • Synchronized(未優(yōu)化前)最主要的問(wèn)題是:在存在線程競(jìng)爭(zhēng)的情況下會(huì)出現(xiàn)線程阻塞和喚醒鎖帶來(lái)的性能問(wèn)題,因?yàn)檫@是一種互斥同步(阻塞同步)。而CAS并不是武斷的間線程掛起,當(dāng)CAS操作失敗后會(huì)進(jìn)行一定的嘗試,而非進(jìn)行耗時(shí)的掛起喚醒的操作,因此也叫做非阻塞同步。這是兩者主要的區(qū)別;
  • 使用CAS時(shí)非阻塞同步,也就是說(shuō)不會(huì)將線程掛起,會(huì)自旋(無(wú)非就是一個(gè)死循環(huán))進(jìn)行下一次嘗試,如果這里自旋時(shí)間過(guò)長(zhǎng)對(duì)性能是很大的消耗。如果JVM能支持處理器提供的pause指令,那么在效率上會(huì)有一定的提升;
  • CAS它當(dāng)中使用了3個(gè)基本操作數(shù):內(nèi)存地址 V,舊的預(yù)期值 A,要修改的新值 B。采用的是一種樂(lè)觀鎖的機(jī)制,它不會(huì)阻塞任何線程,所以在效率上,它會(huì)比 synchronized 要高。所謂樂(lè)觀鎖就是:每次不加鎖而是假設(shè)沒(méi)有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試,直到成功為止;

4、為什么需要CAS機(jī)制

我們經(jīng)常使用volatile關(guān)鍵字修飾某一個(gè)變量,表明這個(gè)變量是全局共享的一個(gè)變量,同時(shí)具有了可見(jiàn)性和有序性。但是卻沒(méi)有原子性。比如說(shuō)一個(gè)常見(jiàn)的操作a++。這個(gè)操作其實(shí)可以細(xì)分成三個(gè)步驟:

(1)從內(nèi)存中讀取a

(2)對(duì)a進(jìn)行加1操作

(3)將a的值重新寫入內(nèi)存中

在單線程狀態(tài)下這個(gè)操作沒(méi)有一點(diǎn)問(wèn)題,但是在多線程中就會(huì)出現(xiàn)各種各樣的問(wèn)題了。因?yàn)榭赡芤粋€(gè)線程對(duì)a進(jìn)行了加1操作,還沒(méi)來(lái)得及寫入內(nèi)存,其他的線程就讀取了舊值。造成了線程的不安全現(xiàn)象;

Volatile關(guān)鍵字可以保證線程間對(duì)于共享變量的可見(jiàn)性可有序性,可以防止CPU的指令重排序(DCL單例),但是無(wú)法保證操作的原子性,所以jdk1.5之后引入CAS利用CPU原語(yǔ)保證線程操作的院子性;

CAS操作由處理器提供支持,是一種原語(yǔ)。原語(yǔ)是操作系統(tǒng)或計(jì)算機(jī)網(wǎng)絡(luò)用語(yǔ)范疇。是由若干條指令組成的,用于完成一定功能的一個(gè)過(guò)程,具有不可分割性,即原語(yǔ)的執(zhí)行必須是連續(xù)的,在執(zhí)行過(guò)程中不允許被中斷。如 Intel 處理器,比較并交換通過(guò)指令的 cmpxchg 系列實(shí)現(xiàn);

二、cas底層實(shí)現(xiàn)

1、底層依靠Unsafe的CAS操作來(lái)保證原子性;

CAS的實(shí)現(xiàn)主要在JUC中的atomic包,我們以AtomicInteger類為例:

  1. /** 
  2.  * Atomically adds the given value to the current value. 
  3.  * 
  4.  * @param delta the value to add 
  5.  * @return the previous value 
  6.  */ 
  7. public final int getAndAdd(int delta) { 
  8.     return unsafe.getAndAddInt(this, valueOffset, delta); 
  9. public final int incrementAndGet() { 
  10.     for (;;) { 
  11.         int current = get(); 
  12.         int next = current + 1; 
  13.         if (compareAndSet(currentnext)) 
  14.             return next
  15.     } 
  16. private volatile int value; 
  17. public final int get() { 
  18.     return value; 

代碼是一個(gè)無(wú)限循環(huán),也就是CAS的自旋。循環(huán)體當(dāng)中做了三件事:

獲取當(dāng)前值;

當(dāng)前值+1,計(jì)算出目標(biāo)值;

進(jìn)行CAS操作,如果成功則跳出循環(huán)(當(dāng)前值和目標(biāo)值相等),如果失敗則重復(fù)上述步驟;

2、Unsafe.class

  1. public final int getAndAddInt(Object var1, long var2, int var4) { 
  2.     int var5; 
  3.     do { 
  4.         var5 = this.getIntVolatile(var1, var2); 
  5.     } while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//native方法 
  6.     return var5; 
  7. }    
  8. ******** 
  9. public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);//底層c++實(shí)現(xiàn) 

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);//底層c++實(shí)現(xiàn)

3、compareAndSwapInt為native方法,對(duì)應(yīng)底層hotspot虛擬機(jī)unsage.cpp

  1. UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) 
  2.   UnsafeWrapper("Unsafe_CompareAndSwapInt"); 
  3.   oop p = JNIHandles::resolve(obj); 
  4.   jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); 
  5.   return (jint)(Atomic::cmpxchg(x, addr, e)) == e; 
  6. UNSAFE_END 
  7. *** 

這里可以看到最終使用了Atomic::cmpxchg來(lái)保證原子性,可繼續(xù)跟進(jìn)代碼

4、Atomic::cmpxchg針對(duì)不同平臺(tái)有不同的實(shí)現(xiàn)方式

  1. *** 
  2. // Adding a lock prefix to an instruction on MP machine 
  3. #define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: " 
  4. *** 
  5. inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) { 
  6.   int mp = os::is_MP(); 
  7.   __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)" 
  8.                     : "=a" (exchange_value) 
  9.                     : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp) 
  10.                     : "cc""memory"); 
  11.   return exchange_value; 

最重要的指令為 LOCK_IF_MP , MP是指多CPU(multi processors),最終意義為多CPU的情況下需要lock,通過(guò)lock的方式來(lái)保證原子;

lock解釋:

  • 確保后續(xù)指令執(zhí)行的原子性;
  • 在Pentium及之前的處理器中,帶有l(wèi)ock前綴的指令在執(zhí)行期間會(huì)鎖住總線,使得其它處理器暫時(shí)無(wú)法通過(guò)總線訪問(wèn)內(nèi)存,很顯然,這個(gè)開(kāi)銷很大。在新的處理器中,Intel使用緩存鎖定來(lái)保證指令執(zhí)行的原子性,緩存鎖定將大大降低lock前綴指令的執(zhí)行開(kāi)銷;
  • 禁止該指令與前面和后面的讀寫指令重排序;
  • 把寫緩沖區(qū)的所有數(shù)據(jù)刷新到內(nèi)存中;

總之:JAVA中我們使用到涉及到CAS操作的底層實(shí)現(xiàn)為對(duì)應(yīng)平臺(tái)虛擬機(jī)中的c++代碼(lock指令)實(shí)現(xiàn)來(lái)保證原子性;

三、CAS 的缺點(diǎn)及解決方式

CAS雖然很高效的解決原子操作,但是CAS仍然存在三大問(wèn)題。ABA問(wèn)題,循環(huán)時(shí)間長(zhǎng)開(kāi)銷大和只能保證一個(gè)共享變量的原子操作;

1、ABA問(wèn)題:因?yàn)镃AS需要在操作值的時(shí)候檢查下值有沒(méi)有發(fā)生變化,如果沒(méi)有發(fā)生變化則更新,但是如果一個(gè)值原來(lái)是A,變成了B,又變成了A, 那么使用CAS進(jìn)行檢查時(shí)會(huì)發(fā)現(xiàn)它的值沒(méi)有發(fā)生變化,但是實(shí)際上卻變化了;

ABA問(wèn)題的解決思路就是使用版本號(hào)。在變量前面追加上版本號(hào),每次變量更新的時(shí)候把版本號(hào)加一,那么A-B-A 就會(huì)變成1A-2B-3A;

從Java1.5開(kāi)始JDK的atomic包里提供了一個(gè)類 AtomicStampedReference 來(lái)解決ABA問(wèn)題。這個(gè)類的compareAndSet方法作用是首先檢查當(dāng)前引用是否等于預(yù)期引用,并且當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志,如果全部相等,則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值;

2、循環(huán)時(shí)間長(zhǎng)開(kāi)銷大:在并發(fā)量比較高的情況下,如果許多線程反復(fù)嘗試更新某一個(gè)變量,即自旋CAS如果長(zhǎng)時(shí)間不成功,會(huì)給CPU帶來(lái)非常大的執(zhí)行開(kāi)銷;

如果JVM能支持處理器提供的pause指令,那么效率會(huì)有一定的提升。pause指令有兩個(gè)作用,第一它可以延遲流水線執(zhí)行指令(de-pipeline),使CPU不會(huì)消耗過(guò)多的執(zhí)行資源,延遲的時(shí)間取決于具體實(shí)現(xiàn)的版本,在一些處理器上延遲時(shí)間是零。第二它可以避免在退出循環(huán)的時(shí)候因內(nèi)存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執(zhí)行效率;

代碼層面,破壞掉for死循環(huán),當(dāng)自旋超過(guò)一定時(shí)間或者一定次數(shù)時(shí),return退出;

使用類似ConcurrentHashMap的方法。當(dāng)多個(gè)線程競(jìng)爭(zhēng)時(shí),將粒度變小,將一個(gè)變量拆分為多個(gè)變量,達(dá)到多個(gè)線程訪問(wèn)多個(gè)資源的效果,最后再調(diào)用sum把它合起來(lái),能降低CPU消耗,但是治標(biāo)不治本;

3、只能保證一個(gè)共享變量的原子操作:當(dāng)對(duì)一個(gè)共享變量執(zhí)行操作時(shí),我們可以使用循環(huán)CAS的方式來(lái)保證原子操作,但是對(duì)多個(gè)共享變量操作時(shí),循環(huán)CAS就無(wú)法保證操作的原子性;

這個(gè)時(shí)候就可以用鎖,或者有一個(gè)取巧的辦法,就是把多個(gè)共享變量合并成一個(gè)共享變量來(lái)操作。比如有兩個(gè)共享變量i=2,j=a,合并一下ij=2a,然后用CAS來(lái)操作ij;

從Java1.5開(kāi)始JDK提供了AtomicReference類來(lái)保證引用對(duì)象之間的原子性,你可以把多個(gè)變量放在一個(gè)對(duì)象里來(lái)進(jìn)行CAS操作;

四、CAS使用的時(shí)機(jī)

線程數(shù)較少、等待時(shí)間短可以采用自旋鎖進(jìn)行CAS嘗試拿鎖,較于synchronized高效;

線程數(shù)較大、等待時(shí)間長(zhǎng),不建議使用自旋鎖,占用CPU較高;

總結(jié)

CAS可以保證多線程對(duì)數(shù)據(jù)寫操作時(shí)數(shù)據(jù)的一致性;

 

CAS的思想:三個(gè)參數(shù),一個(gè)當(dāng)前內(nèi)存值V、舊的預(yù)期值A(chǔ)、即將更新的值B,當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值修改為B并返回true,否則什么都不做,并返回false;

 

責(zé)任編輯:武曉燕 來(lái)源: Android開(kāi)發(fā)編程
相關(guān)推薦

2021-09-24 08:10:40

Java 語(yǔ)言 Java 基礎(chǔ)

2021-10-15 09:19:17

AndroidSharedPrefe分析源碼

2024-12-31 09:00:12

Java線程狀態(tài)

2021-09-16 06:44:04

Android進(jìn)階流程

2024-06-06 09:58:13

2021-09-15 07:31:33

Android窗口管理

2021-09-30 07:36:51

AndroidViewDraw

2017-11-14 14:41:11

Java泛型IO

2021-09-10 07:31:54

AndroidAppStartup原理

2024-12-30 08:02:40

2017-01-13 22:42:15

iosswift

2017-08-08 09:15:41

前端JavaScript頁(yè)面渲染

2021-09-08 06:51:52

AndroidRetrofit原理

2021-02-17 11:25:33

前端JavaScriptthis

2021-09-11 07:32:15

Java線程線程池

2021-08-18 07:56:04

AndroidRecyclerVie復(fù)用

2023-10-27 07:47:58

Java語(yǔ)言順序性

2020-12-11 07:32:45

編程ThreadLocalJava

2020-11-13 08:42:24

Synchronize

2017-05-03 17:00:16

Android渲染機(jī)制
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)