java鎖的類型aqs(java鎖的類型)

本文目錄一覽:

Java中有哪些鎖,區別是什麼

【1】公平所和非公平所。

公平鎖:是指按照申請鎖的順序來獲取鎖,

非公平所:線程獲取鎖的順序不一定按照申請鎖的順序來的。

//默認是不公平鎖,傳入true為公平鎖,否則為非公平鎖

ReentrantLock reentrantLock = new ReetrantLock();

1

2

【2】共享鎖和獨享鎖

獨享鎖:一次只能被一個線程所訪問

共享鎖:線程可以被多個線程所持有。

ReadWriteLock 讀鎖是共享鎖,寫鎖是獨享鎖。

【3】樂觀鎖和悲觀鎖。

樂觀鎖:對於一個數據的操作並發,是不會發生修改的。在更新數據的時候,會嘗試採用更新,不斷重入的方式,更新數據。

悲觀鎖:對於同一個數據的並發操作,是一定會發生修改的。因此對於同一個數據的並發操作,悲觀鎖採用加鎖的形式。悲觀鎖認為,不加鎖的操作一定會出問題,

【4】分段鎖

1.7及之前的concurrenthashmap。並發操作就是分段鎖,其思想就是讓鎖的粒度變小。

【5】偏向鎖是指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖。降低獲取鎖的代價

輕量級鎖

重量級鎖

【6】自旋鎖

自旋鎖

說說java鎖有哪些種類,以及區別

鎖作為並發共享數據,保證一致性的工具,在JAVA平台有多種實現(如 synchronized 和 ReentrantLock等等 ) 。這些已經寫好提供的鎖為我們開發提供了便利,但是鎖的具體性質以及類型卻很少被提及。本系列文章將分析JAVA下常見的鎖名稱以及特性,為大家答疑解惑。

1、自旋鎖

自旋鎖是採用讓當前線程不停地的在循環體內執行實現的,當循環的條件被其他線程改變時 才能進入臨界區。如下

01 public class SpinLock {

02

03 private AtomicReferenceThread sign =newAtomicReference();

04

05 public void lock(){

06 Thread current = Thread.currentThread();

07 while(!sign .compareAndSet(null, current)){

08 }

09 }

10

11 public void unlock (){

12 Thread current = Thread.currentThread();

13 sign .compareAndSet(current, null);

14 }

15 }

使用了CAS原子操作,lock函數將owner設置為當前線程,並且預測原來的值為空。unlock函數將owner設置為null,並且預測值為當前線程。

當有第二個線程調用lock操作時由於owner值不為空,導致循環一直被執行,直至第一個線程調用unlock函數將owner設置為null,第二個線程才能進入臨界區。

由於自旋鎖只是將當前線程不停地執行循環體,不進行線程狀態的改變,所以響應速度更快。但當線程數不停增加時,性能下降明顯,因為每個線程都需要執行,佔用CPU時間。如果線程競爭不激烈,並且保持鎖的時間段。適合使用自旋鎖。

註:該例子為非公平鎖,獲得鎖的先後順序,不會按照進入lock的先後順序進行。

Java鎖的種類以及辨析(二):自旋鎖的其他種類

鎖作為並發共享數據,保證一致性的工具,在JAVA平台有多種實現(如 synchronized 和 ReentrantLock等等 ) 。這些已經寫好提供的鎖為我們開發提供了便利,但是鎖的具體性質以及類型卻很少被提及。本系列文章將分析JAVA下常見的鎖名稱以及特性,為大家答疑解惑。

2.自旋鎖的其他種類

上篇我們講到了自旋鎖,在自旋鎖中 另有三種常見的鎖形式:TicketLock ,CLHlock 和MCSlock

Ticket鎖主要解決的是訪問順序的問題,主要的問題是在多核cpu上

01 package com.alipay.titan.dcc.dal.entity;

02

03 import java.util.concurrent.atomic.AtomicInteger;

04

05 public class TicketLock {

06 private AtomicInteger serviceNum = new AtomicInteger();

07 private AtomicInteger ticketNum = new AtomicInteger();

08 private static final ThreadLocalInteger LOCAL = new ThreadLocalInteger();

09

10 public void lock() {

11 int myticket = ticketNum.getAndIncrement();

12 LOCAL.set(myticket);

13 while (myticket != serviceNum.get()) {

14 }

15

16 }

17

18 public void unlock() {

19 int myticket = LOCAL.get();

20 serviceNum.compareAndSet(myticket, myticket + 1);

21 }

22 }

每次都要查詢一個serviceNum 服務號,影響性能(必須要到主內存讀取,並阻止其他cpu修改)。

CLHLock 和MCSLock 則是兩種類型相似的公平鎖,採用鏈表的形式進行排序,

01 importjava.util.concurrent.atomic.AtomicReferenceFieldUpdater;

02

03 public class CLHLock {

04 public static class CLHNode {

05 private volatile boolean isLocked = true;

06 }

07

08 @SuppressWarnings(“unused”)

09 private volatileCLHNode tail;

10 private static finalThreadLocalCLHNode LOCAL = new ThreadLocalCLHNode();

11 private static finalAtomicReferenceFieldUpdaterCLHLock, CLHNode UPDATER = AtomicReferenceFieldUpdater.newUpdater(CLHLock.class,

12 CLHNode.class,”tail”);

13

14 public void lock() {

15 CLHNode node = new CLHNode();

16 LOCAL.set(node);

17 CLHNode preNode = UPDATER.getAndSet(this, node);

18 if (preNode != null) {

19 while (preNode.isLocked) {

20 }

21 preNode = null;

22 LOCAL.set(node);

23 }

24 }

25

26 public void unlock() {

27 CLHNode node = LOCAL.get();

28 if (!UPDATER.compareAndSet(this, node,null)) {

29 node.isLocked = false;

30 }

31 node = null;

32 }

33 }

CLHlock是不停的查詢前驅變數, 導致不適合在NUMA 架構下使用(在這種結構下,每個線程分布在不同的物理內存區域)

MCSLock則是對本地變數的節點進行循環。不存在CLHlock 的問題。

01 importjava.util.concurrent.atomic.AtomicReferenceFieldUpdater;

02

03 public class MCSLock {

04 public static class MCSNode {

05 volatile MCSNode next;

06 volatile boolean isLocked = true;

07 }

08

09 private static finalThreadLocalMCSNode NODE = new ThreadLocalMCSNode();

10 @SuppressWarnings(“unused”)

11 private volatileMCSNode queue;

12 private static finalAtomicReferenceFieldUpdaterMCSLock, MCSNode UPDATER = AtomicReferenceFieldUpdater.newUpdater(MCSLock.class,

13 MCSNode.class,”queue”);

14

15 public void lock() {

16 MCSNode currentNode = new MCSNode();

17 NODE.set(currentNode);

18 MCSNode preNode = UPDATER.getAndSet(this, currentNode);

19 if (preNode != null) {

20 preNode.next = currentNode;

21 while (currentNode.isLocked) {

22

23 }

24 }

25 }

26

27 public void unlock() {

28 MCSNode currentNode = NODE.get();

29 if (currentNode.next == null) {

30 if (UPDATER.compareAndSet(this, currentNode, null)) {

31

32 } else {

33 while (currentNode.next == null) {

34 }

35 }

36 } else {

37 currentNode.next.isLocked = false;

38 currentNode.next = null;

39 }

40 }

41 }

在java中有哪些鎖

給你整理了Java中的一些鎖:

公平鎖/非公平鎖

可重入鎖

獨享鎖/共享鎖

互斥鎖/讀寫鎖

樂觀鎖/悲觀鎖

分段鎖

偏向鎖/輕量級鎖/重量級鎖

自旋鎖

上面是很多鎖的名詞,這些分類並不是全是指鎖的狀態,有的指鎖的特性,有的指鎖的設計

LockSupport與AQS

LockSupport類是Java6(JSR166-JUC)引入的一個類,提供了基本的線程同步原語。

每個線程都會有一個獨有的permit(許可)。

相比較於wait/notify/notifyAll有何優點?

注意:LockSupport是不可重入的:unpark三次之後,park一次可以繼續運行,再次park還是會被阻塞。可以理解為unpark是把某個標誌位標為1,並不是加1。park是將這個標誌位標為0,而非減1。

AbstractQueuedSynchronizer,即隊列同步器。它是構建鎖或者其他同步組件的基礎框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),它是JUC並發包中的核心基礎組件。

AQS簡單地說就是使用一個FIFO的等待隊列和一個volatile int state來實現同步的。即通過CAS state判斷是否被鎖(CAS來保證原子性、volatile保證可見性),將阻塞的線程打包放入等待隊列中。

1. AQS的使用者一般定義一個內部類來繼承AQS,使用組合的方式使用。

2. AQS有兩種模式:排他和共享。

排他模式:只有一個線程可以擁有鎖。(排他鎖)

共享模式:可以同時多個線程擁有鎖。(讀鎖)

AQS中兩種模式下的waiting thread共用一個queue,所以一般使用者都只是使用一種模式。ReentrantReadWriteLock是同時使用了兩種模式。

使用者繼承AQS,實現AQS中的幾個未實現的方法。然後就可以調用AQS的方法來實現自己的介面功能了。

我們可以看到ReentrantLock使用一個內部類Sync來繼承AQS,然後實現排他鎖的三個方法。

我們知道ReentrantLock有lock和unlock介面,可以看到這兩個介面的實現就是調用AQS原有的方法。

前三個是排他鎖所要實現的,後兩個是共享鎖所要實現了。注意:這五個函數並不是abstract,原因是因為一般都是使用某一種模式(排他或共享模式),所以子類只需使用其中一組就可以了。

在使用AQS的類中用來加鎖和解鎖的方法。

這裡我們可以看到「獲取鎖,如果失敗則加入隊列」這個行為是由AQS來實現的。而如何判斷失敗?這個是由子類來決定的。這個決定支持了可重入性、是否公平性等功能。

共享模式下的對應的四個方法。

我們知道公平鎖:先來的一定先獲取鎖。

非公平鎖:當多個線程在爭取鎖,誰先獲取鎖的順序是不固定的。

AQS的公平性是由使用者來決定的。

我們知道AQS中的acquire函數是大致這樣實現的。

因為每次acquire的步驟是:先try再入隊列。所以就可以出現這種情況:隊列中有兩個線程在等待,當鎖被釋放時,剛好又來了一個線程,則try的時候成功了,這樣這個線程就獲得鎖了。

如果想要實現公平鎖:tryAcquire的時候判斷一下,如果有線程在等待,這個函數直接返回false。

顯然非公平鎖要比公平鎖的效果要高。

什麼是重入鎖和AQS

什麼是重入鎖

java.util.concurrent.locks.ReentrantLock

ReenTrantLock獨有的能力:

1.      ReenTrantLock可以指定是公平鎖還是非公平鎖。而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的線程先獲得鎖。

2.      ReenTrantLock提供了一個Condition(條件)類,用來實現分組喚醒需要喚醒的線程們,而不是像synchronized要麼隨機喚醒一個線程要麼喚醒全部線程。

3.      ReenTrantLock提供了一種能夠中斷等待鎖的線程的機制,通過lock.lockInterruptibly()來實現這個機制。

非重入鎖

當A方法獲取鎖去鎖住一段需要做原子性操作的B方法時,如果這段B方法又需要鎖去做原子性操作,那麼A方法就必定要與B方法出現死鎖。這種會出現問題的重入一把鎖的情況,叫不可重入鎖。

lock的過程:

   首先嘗試獲取資源,如果當前狀態為0,表示沒有線程佔有鎖,設置該線程為獨佔模式,使用CAS設置狀態,否則如果當前線程和獨佔線程是一個線程,修改狀態值,否則返回false。

  若獲取資源失敗,則通過addWaiter -(aqs)方法創建一個節點並放在CLH隊列的尾部。head tail未初始化會創建虛擬節點同時指向

為什麼 AQS 需要一個虛擬 head 節點

每個節點都需要設置前置Node 的 waitStatus  狀態(這個狀態為是為了保證數據一致性),防止重複釋放操作。而第一個節點是沒有前置節點的,所以需要創建一個虛擬節點。

  逐步去執行CLH隊列中的線程,當前線程會公平性的阻塞一直到獲取鎖為止,返回線程在等待的過程中還是否中斷過。

unlock的過程

一次unlock操作需要修改狀態位,然後喚醒節點。整個釋放操作也是使用unpark()來喚醒隊列最前面的節點。其實lock中比較重要的也就是lock和release,它們又和AQS聯繫緊密,下面會單獨談談AQS的重要方法。

Condition的await和signal

wait和notify/notify VS await和signal

Condition能夠支持不響應中斷,而通過使用Object方式不支持;

Condition能夠支持多個等待隊列(new 多個Condition對象),而Object方式只能支持一個;

Condition能夠支持超時時間的設置,而Object不支持

對標Object的wait方法

void await() throws InterruptedException:當前線程進入等待狀態,如果其他線程調用condition的signal或者signalAll方法並且當前線程獲取Lock從await方法返回,如果在等待狀態中被中斷會拋出被中斷異常;

long awaitNanos(long nanosTimeout):當前線程進入等待狀態直到被通知,中斷或者超時;

boolean await(long time, TimeUnit unit)throws InterruptedException:同第二種,支持自定義時間單位

boolean awaitUntil(Date deadline) throws InterruptedException:當前線程進入等待狀態直到被通知,中斷或者到了某個時間

對標Object的notify/notifyAll方法

void signal():喚醒一個等待在condition上的線程,將該線程從等待隊列中轉移到同步隊列中,如果在同步隊列中能夠競爭到Lock則可以從等待方法中返回。

void signalAll():與1的區別在於能夠喚醒所有等待在condition上的線程

如圖所示,ConditionObject是AQS的內部類,因此每個ConditionObject能夠訪問到AQS提供的方法,相當於每個Condition都擁有所屬同步器的引用。

調用condition.await方法的線程必須是已經獲得了lock,也就是當前線程是同步隊列中的頭結點。調用該方法後會使得當前線程所封裝的Node尾插入到等待隊列中。

如圖,線程awaitThread先通過lock.lock()方法獲取鎖成功後調用了condition.await方法進入等待隊列,而另一個線程signalThread通過lock.lock()方法獲取鎖成功後調用了condition.signal或者signalAll方法,使得線程awaitThread能夠有機會移入到同步隊列中,當其他線程釋放lock後使得線程awaitThread能夠有機會獲取lock,從而使得線程awaitThread能夠從await方法中退出執行後續操作。如果awaitThread獲取lock失敗會直接進入到同步隊列。

// 線程已被取消

    static final int CANCELLED =  1;

    // 當前線程的後繼線程需要被unpark(喚醒)

    // 一般發生情況是:當前線程的後繼線程處於阻塞狀態,而當前線程被release或cancel掉,因此需要喚醒當前線程的後繼線程。

    static final int SIGNAL    = -1;

    // 在Condition休眠狀態,在等待Condition喚醒

    static final int CONDITION = -2;

    // (共享鎖)其它線程獲取到「共享鎖」,對應的waitStatus的值

    static final int PROPAGATE = -3;

volatile int waitStatus;

———————

/**

    * 這個方法也就是lock()方法的關鍵方法。tryAcquire獲得資源,返回true,直接結束。若未獲取資源,新建一個節點插入隊尾,

*addWaiter用於添加節點,也就是把當前線程對應的節點插入CLH隊列的尾部。

    * @param arg the acquire argument.  This value is conveyed to

    *        {@link #tryAcquire} but is otherwise uninterpreted and

    *        can represent anything you like.

    */

    public final void acquire(int arg) {

        if (!tryAcquire(arg) //獲取資源立刻結束

            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//沒有被中斷過,也結束

            selfInterrupt();

    }

———————

protected final boolean tryAcquire(int acquires) {

            final Thread current = Thread.currentThread();

            int c = getState();

            if (c == 0) {

                if (!hasQueuedPredecessors()

                    compareAndSetState(0, acquires)) {

                    setExclusiveOwnerThread(current);

                    return true;

                }

            }

            else if (current == getExclusiveOwnerThread()) { //判斷是否持有鎖的是自己,重入

                int nextc = c + acquires;

                if (nextc 0)

                    throw new Error(“Maximum lock count exceeded”);

                setState(nextc);

                return true;

            }

            return false;

        }

———————

  * 非公平鎖

    */

    static final class NonfairSync extends Sync {

        private static final long serialVersionUID = 7316153563782823691L;

        /**

        * Performs lock.  Try immediate barge, backing up to normal

        * acquire on failure.

        */

        final void lock() {

            if (compareAndSetState(0, 1))//CAS設置當前為0 的時候上鎖

                setExclusiveOwnerThread(Thread.currentThread());

            else

                acquire(1);//否則嘗試獲得鎖。

        }

        protected final boolean tryAcquire(int acquires) {

            return nonfairTryAcquire(acquires);

        }

    }

    /**

    * 公平鎖

    */

    static final class FairSync extends Sync {

        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {

            acquire(1);

        }

        /**

        *

        */

        protected final boolean tryAcquire(int acquires) {

            final Thread current = Thread.currentThread();

            int c = getState();

            if (c == 0) {

                if (!hasQueuedPredecessors()

                    compareAndSetState(0, acquires)) {//沒有前驅節點並且CAS設置成功

                    setExclusiveOwnerThread(current);//設置當前線程為獨佔線程

                    return true;

                }

            }

            else if (current == getExclusiveOwnerThread()) {//這裡和非公平鎖類似

                int nextc = c + acquires;

                if (nextc 0)

                    throw new Error(“Maximum lock count exceeded”);

                setState(nextc);

                return true;

            }

            return false;

        }

    }

原創文章,作者:簡單一點,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/129082.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
簡單一點的頭像簡單一點
上一篇 2024-10-03 23:26
下一篇 2024-10-03 23:26

相關推薦

  • Java JsonPath 效率優化指南

    本篇文章將深入探討Java JsonPath的效率問題,並提供一些優化方案。 一、JsonPath 簡介 JsonPath是一個可用於從JSON數據中獲取信息的庫。它提供了一種DS…

    編程 2025-04-29
  • java client.getacsresponse 編譯報錯解決方法

    java client.getacsresponse 編譯報錯是Java編程過程中常見的錯誤,常見的原因是代碼的語法錯誤、類庫依賴問題和編譯環境的配置問題。下面將從多個方面進行分析…

    編程 2025-04-29
  • int類型變數的細節與注意事項

    本文將從 int 類型變數的定義、聲明、初始化、範圍、運算和類型轉換等方面,對 int 類型變數進行詳細闡述和講解,幫助讀者更好地掌握和應用 int 變數。 一、定義與聲明 int…

    編程 2025-04-29
  • Java Bean載入過程

    Java Bean載入過程涉及到類載入器、反射機制和Java虛擬機的執行過程。在本文中,將從這三個方面詳細闡述Java Bean載入的過程。 一、類載入器 類載入器是Java虛擬機…

    編程 2025-04-29
  • Java騰訊雲音視頻對接

    本文旨在從多個方面詳細闡述Java騰訊雲音視頻對接,提供完整的代碼示例。 一、騰訊雲音視頻介紹 騰訊雲音視頻服務(Cloud Tencent Real-Time Communica…

    編程 2025-04-29
  • Java Milvus SearchParam withoutFields用法介紹

    本文將詳細介紹Java Milvus SearchParam withoutFields的相關知識和用法。 一、什麼是Java Milvus SearchParam without…

    編程 2025-04-29
  • Java 8中某一周的周一

    Java 8是Java語言中的一個版本,於2014年3月18日發布。本文將從多個方面對Java 8中某一周的周一進行詳細的闡述。 一、數組處理 Java 8新特性之一是Stream…

    編程 2025-04-29
  • Python3定義函數參數類型

    Python是一門動態類型語言,不需要在定義變數時顯示的指定變數類型,但是Python3中提供了函數參數類型的聲明功能,在函數定義時明確定義參數類型。在函數的形參後面加上冒號(:)…

    編程 2025-04-29
  • Java判斷字元串是否存在多個

    本文將從以下幾個方面詳細闡述如何使用Java判斷一個字元串中是否存在多個指定字元: 一、字元串遍歷 字元串是Java編程中非常重要的一種數據類型。要判斷字元串中是否存在多個指定字元…

    編程 2025-04-29
  • VSCode為什麼無法運行Java

    解答:VSCode無法運行Java是因為默認情況下,VSCode並沒有集成Java運行環境,需要手動添加Java運行環境或安裝相關插件才能實現Java代碼的編寫、調試和運行。 一、…

    編程 2025-04-29

發表回復

登錄後才能評論