CAS和AQS

CAS

全稱(Compare And Swap),比較交換

Unsafe類是CAS的核心類,提供硬件級別的原子操作

?

// 對象、對象的地址、預期值、修改值
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

缺點:

  1. 開銷大:在并發量比較高的情況下,如果反復嘗試更新某個變量,卻又一直更新不成功,會給CPU帶來較大的壓力
  2. ABA問題:當變量從A修改為B在修改回A時,變量值等于期望值A,但是無法判斷是否修改,CAS操作在ABA修改后依然成功。
    • 如何避免:Java提供了AtomicStampedReference和AtomicMarkableReference來解決。AtomicStampedReference通過包裝[E,Integer]的元組來對對象標記版本戳stamp,對于ABA問題其解決方案是加上版本號,即在每個變量都加上一個版本號,每次改變時加1,即A —> B —> A,變成1A —> 2B —> 3A。
  3. 不能保證代碼塊的原子性:CAS機制所保證的只是一個變量的原子性操作,而不能保證整個代碼塊的原子性。

?

public class Test {private static AtomicInteger atomicInteger = new AtomicInteger(100);private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);public static void main(String[] args) throws InterruptedException {//AtomicIntegerThread at1 = new Thread(new Runnable() {@Overridepublic void run() {atomicInteger.compareAndSet(100,110);atomicInteger.compareAndSet(110,100);}});Thread at2 = new Thread(new Runnable() {@Overridepublic void run() {try {TimeUnit.SECONDS.sleep(2);      // at1,執行完} catch (InterruptedException e) {e.printStackTrace();}System.out.println("AtomicInteger:" + atomicInteger.compareAndSet(100,120));}});at1.start();at2.start();at1.join();at2.join();//AtomicStampedReferenceThread tsf1 = new Thread(new Runnable() {@Overridepublic void run() {try {//讓 tsf2先獲取stamp,導致預期時間戳不一致TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}// 預期引用:100,更新后的引用:110,預期標識getStamp() 更新后的標識getStamp() + 1atomicStampedReference.compareAndSet(100,110,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);atomicStampedReference.compareAndSet(110,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);}});Thread tsf2 = new Thread(new Runnable() {@Overridepublic void run() {int stamp = atomicStampedReference.getStamp();try {TimeUnit.SECONDS.sleep(2);      //線程tsf1執行完} catch (InterruptedException e) {e.printStackTrace();}System.out.println("AtomicStampedReference:" +atomicStampedReference.compareAndSet(100,120,stamp,stamp + 1));}});tsf1.start();tsf2.start();}}

AQS(AbstractQueuedSynchronizer)

維護一個volatile int state(代表共享資源狀態)和一個FIFO線程等待隊列。

模板方法基本分為三類:

  • 獨占鎖
  • 共享鎖
  • 釋放鎖

資源共享的方式

  1. Exclusive(獨占,只有一個線程能執行,如ReentrantLock)
  2. Share(共享,多個線程可以同時執行,如Semaphore/CountDownLatch)

同步隊列

AQS依靠同步隊列(一個FIFO的雙向隊列)來完成同步狀態的管理。當當前線程獲取狀態失敗后,同步器會將當前線程以及等待信息構造成一個節點(Node),并嘗試將他加入到同步隊列。Head節點不保存等待的線程信息,僅通過next指向隊列中第一個保存等待線程信息的Node。

?

雙向同步隊列

Node類

源碼(中字注釋)

?

static final class Node {/** 代表共享模式 */static final Node SHARED = new Node();/** 代表獨占模式 */static final Node EXCLUSIVE = null;/** 以下四個狀態解釋見下文等待狀態 */static final int CANCELLED =  1;static final int SIGNAL    = -1;static final int CONDITION = -2;static final int PROPAGATE = -3;/** 標識等待狀態,通過CAS操作更新,原子操作不會被打斷*/volatile int waitStatus;/** 當前節點的前置節點 */volatile Node prev;/** 當前節點的后置節點 */volatile Node next;/** 該節點關聯的線程(未能獲取鎖,進入等待的線程) */volatile Thread thread;/** 指向下一個在某個條件上等待的節點,或者指向 SHARE 節點,表明當前處于共享模式*/Node nextWaiter;/*** 判斷是否處于共享模式*/final boolean isShared() {return nextWaiter == SHARED;}/** * 返回當前節點的前置節點 * 會做對前置節點空值判斷 */final Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}Node() {    // Used to establish initial head or SHARED marker}Node(Thread thread, Node mode) {     // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}Node(Thread thread, int waitStatus) { // Used by Conditionthis.waitStatus = waitStatus;this.thread = thread;}
}

等待狀態:

等待狀態的修改是CAS原子操作

  • CANCELED: 1,因為等待超時 (timeout)或者中斷(interrupt),節點會被置為取消狀態。處于取消狀態的節點不會再去競爭鎖,也就是說不會再被阻塞。節點會一直保持取消狀態,而不會轉換為其他狀態。處于 CANCELED 的節點會被移出隊列,被 GC 回收。
  • SIGNAL: -1,表明當前的后繼結點正在或者將要被阻塞(通過使用 LockSupport.pack 方法),因此當前的節點被釋放(release)或者被取消時(cancel)時,要喚醒它的后繼結點(通過 LockSupport.unpark 方法)。
  • CONDITION: -2,表明當前節點在條件隊列中,因為等待某個條件而被阻塞。
  • PROPAGATE: -3,在共享模式下,可以認為資源有多個,因此當前線程被喚醒之后,可能還有剩余的資源可以喚醒其他線程。該狀態用來表明后續節點會傳播喚醒的操作。需要注意的是只有頭節點才可以設置為該狀態(This is set (for head node only) in doReleaseShared to ensure propagation continues, even if other operations have since intervened.)。
  • 0:新創建的節點會處于這種狀態

鎖的獲取與釋放:

獲取獨占鎖

?

獲取獨占鎖

  • acquire方法

?

public final void acquire(int arg) {// 首先嘗試獲取鎖,如果獲取失敗,會先調用 addWaiter 方法創建節點并追加到隊列尾部// 然后調用 acquireQueued 阻塞或者循環嘗試獲取鎖if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){// 在 acquireQueued 中,如果線程是因為中斷而退出的阻塞狀態會返回 true// 這里的 selfInterrupt 主要是為了恢復線程的中斷狀態selfInterrupt();}
}

釋放獨占鎖

?

釋放獨占鎖

? 在獨占模式中,鎖的釋放由于沒有其他線程競爭,相對簡單。鎖釋放失敗的原因是由于該線程本身不擁有鎖,而非多線程競爭。鎖釋放成功后會檢查后置節點的狀態,找到合適的節點,調用unparkSuccessor方法喚醒該節點所關聯的線程。

  • release方法

?

public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;// waitStatus 為 0,證明是初始化的空隊列或者后繼結點已經被喚醒了if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;
}

獲取共享鎖

?

獲取共享鎖

  • acquireShared方法

    通過該方法可以申請鎖

?

public final void acquireShared(int arg) {// 如果返回結果小于0,證明沒有獲取到共享資源if (tryAcquireShared(arg) < 0)doAcquireShared(arg);
}
  • doAcquireShared

?

private void doAcquireShared(int arg) {final Node node = addWaiter(Node.SHARED);boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();if (p == head) {int r = tryAcquireShared(arg);if (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCif (interrupted)selfInterrupt();failed = false;return;}}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}
}

釋放共享鎖



作者:RealityVibe
鏈接:https://www.jianshu.com/p/2a48778871a9
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/386531.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/386531.shtml
英文地址,請注明出處:http://en.pswp.cn/news/386531.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

系統盤點Android開發者必須掌握的知識點,全網瘋傳

最近在知乎上&#xff0c;有許多人在邀請我去回答“Android前景怎么樣、是不是要涼了、是不是應該考慮要轉行&#xff1f;”等一系列的問題。 想著可能有很多人都有這樣的擔心&#xff0c;于是就趕緊寫篇文章&#xff0c;來跟你們談下Android開發的前景到底怎么樣&#xff1f;…

CountDownLatch的理解和使用 多線程同步器

CountDownLatch的理解和使用 在筆者想要了解Thrift時候&#xff0c;找到一個博主寫的系統間通信技術的架構設計&#xff0c;在了解和學習的過程中遇到很多小問題和基礎知識&#xff0c;自己還是不夠清楚&#xff0c;就查詢和總結下。 因為筆者也都是從網上找的一些資料&#…

數據庫操作DDL

show database; 查看所有數據庫 drop database db_name; 刪除數據庫 create database db_name;創建數據庫 一個數據庫對應一個文件夾 create database if not exists db_name; show warnings; 查看所有警告 show create databae db_name;查看創建的數據庫 create database if n…

細數Android開發者的艱辛歷程,已拿offer附真題解析

籠統來說&#xff0c;中年程序員容易被淘汰的原因其實不外乎三點。 1、輸出能力已到頂點。這個人奮斗十來年了&#xff0c;依舊碌碌無為&#xff0c;很明顯這人的天花板就這樣了&#xff0c;說白了&#xff0c;天賦就這樣。 2、適應能力越來越差。年紀大&#xff0c;有家庭&…

原子操作類AtomicInteger詳解

為什么需要AtomicInteger原子操作類&#xff1f; 對于Java中的運算操作&#xff0c;例如自增或自減&#xff0c;若沒有進行額外的同步操作&#xff0c;在多線程環境下就是線程不安全的。num解析為numnum1&#xff0c;明顯&#xff0c;這個操作不具備原子性&#xff0c;多線程并…

移動端Rem之講解總結

日媽常說的H5頁面&#xff0c;為啥叫H5頁面嘛&#xff0c;不就是手機上展示的頁面嗎&#xff1f;那是因為啊手機兼容所有html5新特性&#xff0c;所以跑在手機上的頁面也叫h5頁面&#xff0c;跨平臺&#xff08;安裝ios),基于webview&#xff0c;它就是終端開發的一個組件&…

終于有人把安卓程序員必學知識點全整理出來了,送大廠面經一份!

除了Bug&#xff0c;最讓你頭疼的問題是什么&#xff1f;單身&#xff1f;禿頭&#xff1f;996?面試造火箭&#xff0c;工作擰螺絲&#xff1f; 作為安卓開發者&#xff0c;除了Bug&#xff0c;經常會碰到下面這些問題&#xff1a; 應用卡頓&#xff0c;丟幀&#xff0c;屏幕畫…

ABA問題

CAS&#xff1a;對于內存中的某一個值V&#xff0c;提供一個舊值A和一個新值B。如果提供的舊值V和A相等就把B寫入V。這個過程是原子性的。 CAS執行結果要么成功要么失敗&#xff0c;對于失敗的情形下一班采用不斷重試。或者放棄。 ABA&#xff1a;如果另一個線程修改V值假設原…

mq引入以后的缺點

系統可用性降低? 一旦mq不能使用以后,系統A不能發送消息到mq,系統BCD無法從mq中獲取到消息.整個系統就崩潰了. 如何解決: 系統復雜程度增加? 加入mq以后,mq引入來的問題很多,然后導致系統的復雜程度增加. 如何解決 系統的一致性降低? 有人給系統A發送了一個請求,本來這個請求…

網易云的朋友給我這份339頁的Android面經,成功入職阿里

IT行業的前景 近幾年來&#xff0c;大數據、人工智能AI、物聯網等一些技術不斷發展&#xff0c;也讓人們看到了IT行業的繁榮與良好的前景。越來越多的高校學府加大了對計算機的投入&#xff0c;設立相應的熱門專業來吸引招生。當然也有越來越多的人選擇從事這個行業&#xff0…

AQS相關邏輯解析

關心QPS TPS 如何讓線程停留在lock 1、競爭鎖-(拿到鎖的線程、沒拿到鎖的線程) 臨界區的資源&#xff08;static redis 數據庫變量 配置中心config zookeeper&#xff09;大家共享都可以獲得的資源 臨界區沒拿到鎖的未拿到鎖線程進行停留 2、怎么讓線程停留在Lock方法里 …

git介紹和常用操作

轉載于:https://www.cnblogs.com/kesz/p/11124423.html

網易云的朋友給我這份339頁的Android面經,滿滿干貨指導

想要成為一名優秀的Android開發&#xff0c;你需要一份完備的知識體系&#xff0c;在這里&#xff0c;讓我們一起成長為自己所想的那樣~。 25%的面試官會在頭5分鐘內決定面試的結果60%的面試官會在頭15分鐘內決定面試的結果 一般來說&#xff0c;一場單面的時間在30分鐘左右&…

synchronized 和Lock區別

synchronized實現原理 Java中每一個對象都可以作為鎖&#xff0c;這是synchronized實現同步的基礎&#xff1a; 普通同步方法&#xff0c;鎖是當前實例對象靜態同步方法&#xff0c;鎖是當前類的class對象同步方法塊&#xff0c;鎖是括號里面的對象 當一個線程訪問同步代碼塊…

美團安卓面試,難道Android真的涼了?快來收藏!

我所接觸的Android開發者&#xff0c;百分之九十五以上 都遇到了以下幾點致命弱點&#xff01; 如果這些問題也是阻止你升職加薪&#xff0c;跳槽大廠的阻礙。 那么我確信可以幫你突破瓶頸&#xff01; 1.開發者的門越來越高&#xff1a; 小廠的機會少了&#xff0c;大廠…

django -- 實現ORM登錄

前戲 上篇文章寫了一個簡單的登錄頁面&#xff0c;那我們可不可以實現一個簡單的登錄功能呢&#xff1f;如果登錄成功&#xff0c;給返回一個頁面&#xff0c;失敗給出錯誤的提示呢&#xff1f; 在之前學HTML的時候&#xff0c;我們知道&#xff0c;網頁在往服務器提交數據的時…

美團點評APP在移動網絡性能優化的實踐,通用流行框架大全

" 對于程序員來說&#xff0c;如果哪一天開始他停止了學習&#xff0c;那么他的職業生涯便開始宣告消亡。” 高薪的IT行業是眾多年輕人的職業夢想&#xff0c;然而&#xff0c;一旦身入其中卻發覺沒有想像中那么美好。被稱為IT藍領的編程員&#xff0c;工作強度大&#xf…

java 8大happen-before原則超全面詳解

再來重復下八大原則&#xff1a; 單線程happen-before原則&#xff1a;在同一個線程中&#xff0c;書寫在前面的操作happen-before后面的操作。鎖的happen-before原則&#xff1a;同一個鎖的unlock操作happen-before此鎖的lock操作。volatile的happen-before原則&#xff1a;對…

centos7.0利用yum快速安裝mysql8.0

我這里直接使用MySQL Yum存儲庫的方式快速安裝&#xff1a; 抽象 MySQL Yum存儲庫提供用于在Linux平臺上安裝MySQL服務器&#xff0c;客戶端和其他組件的RPM包。這些軟件包還可以升級和替換從Linux發行版本機軟件存儲庫安裝的任何第三方MySQL軟件包&#xff0c;如果可以從MySQL…

騰訊3輪面試都問了Android事件分發,論程序員成長的正確姿勢

前言 這些題目是網友去美團等一線互聯網公司面試被問到的題目。筆者從自身面試經歷、各大網絡社交技術平臺搜集整理而成&#xff0c;熟悉本文中列出的知識點會大大增加通過前兩輪技術面試的幾率。 主要分為以下幾部分&#xff1a; &#xff08;1&#xff09;Android面試題 …