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

CAS與ABA問題及解決方式

系統(tǒng)
要了解ABA問題,我們得先知道什么是CAS,CAS 全稱是 compare and swap,是一種用于在多線程環(huán)境下實現(xiàn)同步功能的機(jī)制。CAS的出現(xiàn)主要是為了解決多線程并發(fā)情況下,數(shù)據(jù)的不一致問題。

[[384754]]

 要了解ABA問題,我們得先知道什么是CAS,CAS 全稱是 compare and swap,是一種用于在多線程環(huán)境下實現(xiàn)同步功能的機(jī)制。CAS的出現(xiàn)主要是為了解決多線程并發(fā)情況下,數(shù)據(jù)的不一致問題。

CAS底層原理

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

Unsafe類

Unsafe類是CAS的核心類,由于Java方法無法直接訪問底層系統(tǒng),需要通過本地(native)方法來訪問,基于該類可以直接操作特定內(nèi)存的數(shù)據(jù)。Unsafe類存在與sum.misc包中,其內(nèi)部實現(xiàn)是C++寫的,我從JDK1.8源碼中截取了關(guān)鍵代碼

  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 

從上面代碼可以看出最后調(diào)用的是Atomic:comxchg這個方法,這個方法的實現(xiàn)放在hotspot下的os_cpu包中,說明這個方法的實現(xiàn)和操作系統(tǒng)、CPU都有關(guān)系,以多核CPU為例:

  • 首先會判斷CPU是否為多核,如果是多核加一個lock內(nèi)存屏障,這樣就可以防止多線程并發(fā)情況競爭發(fā)生
  • 進(jìn)行對比交換,調(diào)用匯編指令cmpxchg獲取新值并設(shè)值。

CAS問題

cas實現(xiàn)

從JDK1.5開始,java.util.concurrent包為我們提供了許多cas操作類諸如:AtomicInteger,

AtomicLong,AtomicReference,它提供了輕量級的鎖機(jī)制有著更好的性能,但同時也會出現(xiàn)一些問題,我們通過一張圖來說明:


上圖運行過程中可能會出現(xiàn)兩個問題:

  • 線程3可能一直拿不到最新的值,導(dǎo)致線程自旋
  • 主內(nèi)存有個數(shù)據(jù)值:A,兩個線程A和B分別copy主內(nèi)存數(shù)據(jù)到自己的工作區(qū),A執(zhí)行比較慢,需要10秒, B執(zhí)行比較快,需要2秒, 此時B線程將主內(nèi)存中的數(shù)據(jù)更改為B,過了一會又更改為A,然后A線程執(zhí)行比較,發(fā)現(xiàn)結(jié)果是A,以為別人沒有動過,然后執(zhí)行更改操作。其實中間已經(jīng)被更改過了,這就是ABA問題。

ABA問題的優(yōu)化

ABA問題導(dǎo)致的原因,是CAS過程中只簡單進(jìn)行了“值”的校驗,再有些情況下,“值”相同不會引入錯誤的業(yè)務(wù)邏輯(例如庫存),有些情況下,“值”雖然相同,卻已經(jīng)不是原來的數(shù)據(jù)了。那如何能避免ABA問題呢?優(yōu)化的方式也很簡單,就是不能只對值進(jìn)行比較,通過對值打標(biāo)簽的方式就能很好的避免ABA問題。JAVA中也為我們提供了相應(yīng)的處理類AtomicStampReferenceAtomicStampReference在cas的基礎(chǔ)上增加了一個標(biāo)記stamp,使用這個標(biāo)記可以用來覺察數(shù)據(jù)是否發(fā)生變化,給數(shù)據(jù)帶上了一種實效性的檢驗。它有以下幾個參數(shù):

  1. //參數(shù)代表的含義分別是 期望值,寫入的新值,期望標(biāo)記,新標(biāo)記值 
  2. public boolean compareAndSet(V expected,V newReference,int expectedStamp,int newStamp); 
  3.  
  4. public V getRerference(); 
  5.  
  6. public int getStamp(); 
  7.  
  8. public void set(V newReference,int newStamp); 

我們通過一個示例來說明:

  1. public class Test { 
  2.  
  3. private static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100); 
  4.  
  5. public static void main(String[] args) { 
  6. new Thread(() -> { 
  7. atomicReference.compareAndSet(100, 101); 
  8. atomicReference.compareAndSet(101, 100); 
  9. },"t1").start(); 
  10.  
  11. new Thread(() -> { 
  12. try { 
  13. TimeUnit.SECONDS.sleep(1); 
  14. } catch (InterruptedException e) { 
  15. e.printStackTrace(); 
  16. System.out.println(atomicReference.compareAndSet(100, 2021) + "\t修改后的值:" + atomicReference.get()); 
  17. },"t2").start(); 
  • 初始值為100,線程t1將100改成101,然后又將101改回100
  • 線程t2先睡眠1秒,等待t1操作完成,然后t2線程將值改成2019

可以看到,線程2修改成功。輸出結(jié)果:

  1. true 修改后的值:2021 

要解決ABA問題,可以增加一個版本號,當(dāng)內(nèi)存位置V的值每次被修改后,版本號都加1AtomicStampedReference內(nèi)部維護(hù)了對象值和版本號,在創(chuàng)建AtomicStampedReference對象時,需要傳入初始值和初始版本號, 當(dāng)AtomicStampedReference設(shè)置對象值時,對象值以及狀態(tài)戳都必須滿足期望值,寫入才會成功

  1. public class Test { 
  2.  
  3.  
  4. private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100,1); 
  5.  
  6. public static void main(String[] args) { 
  7. new Thread(() -> { 
  8. System.out.println("t1拿到的初始版本號:" + atomicStampedReference.getStamp()); 
  9.  
  10. //睡眠1秒,是為了讓t2線程也拿到同樣的初始版本號 
  11. try { 
  12. TimeUnit.SECONDS.sleep(1); 
  13. } catch (InterruptedException e) { 
  14. e.printStackTrace(); 
  15. atomicStampedReference.compareAndSet(100, 101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); 
  16. atomicStampedReference.compareAndSet(101, 100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); 
  17. },"t1").start(); 
  18.  
  19. new Thread(() -> { 
  20. int stamp = atomicStampedReference.getStamp(); 
  21. System.out.println("t2拿到的初始版本號:" + stamp); 
  22.  
  23. //睡眠3秒,是為了讓t1線程完成ABA操作 
  24. try { 
  25. TimeUnit.SECONDS.sleep(3); 
  26. } catch (InterruptedException e) { 
  27. e.printStackTrace(); 
  28. System.out.println("最新版本號:" + atomicStampedReference.getStamp()); 
  29. System.out.println(atomicStampedReference.compareAndSet(100, 2021,stamp,atomicStampedReference.getStamp() + 1) + "\t當(dāng)前 值:" + atomicStampedReference.getReference()); 
  30. },"t2").start(); 
  • 初始值100,初始版本號1
  • 線程t1和t2拿到一樣的初始版本號
  • 線程t1完成ABA操作,版本號遞增到3
  • 線程t2完成CAS操作,最新版本號已經(jīng)變成3,跟線程t2之前拿到的版本號1不相等,操作失敗

輸出結(jié)果:

  1. t1拿到的初始版本號:1 
  2. t2拿到的初始版本號:1 
  3. 最新版本號:3 
  4. false當(dāng)前 值:100 

 【編輯推薦】

 

責(zé)任編輯:姜華 來源: 編碼是個技術(shù)活
相關(guān)推薦

2017-06-23 07:15:52

庫存ABACAS

2024-11-19 17:54:15

JavaCASABA問題

2019-09-05 08:54:38

一致性CASABA

2022-11-16 21:55:51

Redis數(shù)據(jù)庫

2016-11-29 09:00:19

分布式數(shù)據(jù)一致性CAS

2021-02-02 18:02:09

java對象數(shù)據(jù)

2021-02-08 21:07:47

JavaCAS機(jī)制

2014-12-26 10:23:21

谷歌

2017-08-03 09:37:35

SparkStreamKafkaDirect

2010-10-08 16:31:08

AjaxIE6

2012-08-08 14:33:32

IBMdW

2009-07-01 18:14:36

JSP亂碼

2011-05-19 14:16:29

網(wǎng)頁設(shè)計

2013-03-20 09:54:07

2013-04-24 17:05:15

2024-10-30 11:00:00

Python列表索引

2019-12-01 22:08:04

Mavenjar包開發(fā)

2009-12-08 10:52:30

WCF雙工通信

2019-10-08 16:05:19

Redis數(shù)據(jù)庫系統(tǒng)

2013-05-03 13:59:18

視頻會議音頻音頻通話
點贊
收藏

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