Java的原子類無鎖并發(fā)利器詳解
前言引入
當存在如下場景,兩個線程同時去將count值累加一萬次,那么如下代碼是否存在線程安全問題呢?
public class Test2 { public static void main(String[] args) throws InterruptedException { TestCount testCount = new TestCount(); Thread T1 = new Thread(()->{ testCount.add10k(); }); Thread T2 = new Thread(()->{ testCount.add10k(); }); T1.start(); T2.start(); T1.join(); T2.join(); System.out.println(testCount.count); } } class TestCount{ long count = 0; void add10k(){ int idx = 0; while (idx++ < 10000){ count += 1; } } }
顯然是存在的,最終的結(jié)果顯然是小于兩萬的,原因是count值存在可見性問題,count+=1也存在原子性的問題。
一般思路都是將count采用volatile修飾,count+=1原子性問題采用synchronized互斥鎖解決。
class TestCount{ volatile long count = 0; synchronized void add10k(){ int idx = 0; while (idx++ < 10000){ count += 1; } } }
但是對于這種簡單的互斥操作,需要采用synchronized這種比較重的互斥鎖嗎?有沒有更優(yōu)的解決辦法呢?當然存在,采用原子類無鎖方案能夠極大的提升性能
class TestCount{ AtomicLong count = new AtomicLong(0); void add10k(){ int idx = 0; while (idx++ < 10000){ count.getAndIncrement(); } } }
原子類同樣能夠解決互斥性問題、原子性問題除此之外,因為原子類是無鎖操作,沒有用互斥鎖解決帶來的加鎖解決性能消耗,這種絕佳方案是怎么做到的呢?
無鎖方案實現(xiàn)原理
無鎖方案之所以能夠保證原子性,主要還是硬件保證,CPU為了解決并發(fā)問題,提供了CAS(Compare And Swap)指令即比較并交換,CAS一般包含三個參數(shù),共享變量的內(nèi)存地址A,用于比較的期望值B,更新共享變量C,當共享變量的內(nèi)存地址A的值和共享變量B的值相等時,才將共享變量的內(nèi)存地址A處的值更新為共享變量C。
將場景語義化如下
class SimpleCAS{ int count; public synchronized int cas(int expect,int newCount){ // 讀取count值 int oldcount = count; // 讀取的count值和期望值比較 if (oldcount == expect){ count = newCount; } // 返回老值 return oldcount; } }
CAS指令判斷并不是一次性的,如果比較失敗又會重新取最新的值和期望值判斷直到成功。
class SimpleCAS{ volatile int count; public synchronized int cas(int expect,int newCount){ // 讀取count值 int oldcount = count; // 讀取的count值和期望值比較 if (oldcount == expect){ count = newCount; } // 返回老值 return oldcount; } // 自旋操作,執(zhí)行cas方法 public void addOne(){ int newCount = 0; do { newCount = count + 1; }while (count != cas(count,newCount)); } }
ABA問題
原子類雖然好用,但是一定需要的坑就是ABA問題,假如存在共享變量A值為5,線程T1將共享變量A的值改為2,而線程T2將共享變量A改為3,線程T3又將共享變量A改為2,那么對于線程T1來講共享變量A是沒有變的嗎?顯然不是,可能大多數(shù)場景我們并不關(guān)心ABA問題,對于基礎(chǔ)數(shù)據(jù)遞增可能認為值不變就夠了,并不關(guān)心值是否已經(jīng)修改,但是對于引用類型呢,這就一定要注意ABA問題,兩個A雖然相等,但是屬性可能已經(jīng)發(fā)生變化。
原子類提供工具類解決ABA問題AtomicStampedReference和AtomicMarkableReference
public static void main(String[] args) throws InterruptedException { // 初始化原子類 定義初始引用和標識 // AtomicMarkableReference同理可得,只是將版本戳換成了boolean類型 AtomicStampedReference<String> reference = new AtomicStampedReference<>("zhangsan",1001); /** * expectedReference 期望的引用 * newReference 新的引用 * expectedStamp 期望的版本戳 * newStamp 新的版本戳 * 只有當期望引用和期望版本戳都符合實際版本戳和引用才能替換成功 */ reference.compareAndSet("zhangsan","lisi",1002,1003); // zhangsan 替換失敗的原因是期望版本戳和實際版本戳不匹配 System.out.println(reference.getReference()); reference.compareAndSet("zhangsan","lisi",1001,1002); // lisi 替換成功 System.out.println(reference.getReference()); reference.compareAndSet("lisi1","wangwu",1002,1003); // lisi 替換失敗的原因是期望引用和實際引用不匹配 System.out.println(reference.getReference()); }
getAndIncrement源碼分析
/** * this 指當前對象 * valueOffset 指內(nèi)存地址偏移量 */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } /** * this 指當前對象 * valueOffset 指內(nèi)存地址偏移量 */ public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { // 就是讀取主內(nèi)存的值 var5 = this.getIntVolatile(var1, var2); // this.compareAndSwapInt方法就是對應(yīng)上訴的cas方法,不過返回值是boolean類型 // var5讀取的主內(nèi)存值 // 更新成功返回true,跳出循環(huán) } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
原子工具類總覽
原子工具類是一個大家族,根據(jù)使用可以分為原子化基本數(shù)據(jù)類型、原子化的對象引用類型、原子化數(shù)組、原子化對象屬性更新器和原子化的累加器,方法都基本類似不需要刻意去記,需要用到的時候再來查就可以,但是需要有個印象,如下圖所示。
到此這篇關(guān)于Java的原子類無鎖并發(fā)利器詳解的文章就介紹到這了,更多相關(guān)原子類無鎖并發(fā)利器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud配置客戶端ConfigClient接入服務(wù)端
這篇文章主要為大家介紹了SpringCloud配置客戶端ConfigClient接入服務(wù)端,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08Spring?Security中使用authorizeRequests遇到的問題小結(jié)
Spring?是非常流行和成功的?Java?應(yīng)用開發(fā)框架,Spring?Security?正是?Spring?家族中的成員,這篇文章主要介紹了Spring?Security中使用authorizeRequests遇到的問題,需要的朋友可以參考下2023-02-02Java之Error與Exception的區(qū)別案例詳解
這篇文章主要介紹了Java之Error與Exception的區(qū)別案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-09-09SpringBoot?AOP?Redis實現(xiàn)延時雙刪功能實戰(zhàn)
本文主要介紹了SpringBoot?AOP?Redis實現(xiàn)延時雙刪功能實戰(zhàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-08-08