unit類型是什么?_面試官虛晃一槍:項目中有用過鎖嗎?能解釋一下什么是AQS?...

1e932105379ae1cdaac2cf596d582247.png

1 前言

鎖是用來控制多個線程訪問共享資源的方式,一般來說,一個鎖能防止多個線程同時訪問共享資源(但是有些鎖可以允許多個線程并發的訪問共享資源,如讀寫鎖)。在以前,Java程序是靠synchronized來實現鎖功能的,而在Java SE 5之后,并發包中新增了Lock接口(以及相關實現類)用來實現鎖功能,他提供了與synchronized關鍵字類似的同步功能,只是在使用時需要顯式的獲取鎖和釋放鎖,雖然它缺少了synchronized提供的隱式獲取釋放鎖的便捷性,但是卻擁有了鎖獲取和釋放的可操作性、可中斷的獲取鎖以及超時獲取鎖等多種synchronized關鍵字不具備的同步特性。很多鎖都通過實現Lock接口來完成對鎖的操作,比如可重入鎖(ReentrantLock)、前一張講的Redisson分布式鎖等,而Lock接口的實現,基本是都是通過聚合了一個同步器的子類來完成線程訪問控制的,而同步器,就是我們常說的AQS(AbstractQueuedSynchronizer),也是今天要記錄的內容。

2 什么是AQS

AQS(隊列同步器AbstractQueuedSynchronizer)是用來構建鎖或者其他同步組件的基礎框架,它使用了一個int成員變量來表示同步狀態,通過內置的FIFO隊列來完成資源獲取線程的排隊工作。

b4b3bc382a41d30ac1b0757376044927.png

如上圖所示,同步器的主要使用方式是繼承,子類通過繼承同步器并實現它的抽象方法來管理同步狀態,在抽象方法的實現過程中免不了要對同步狀態進行修改,這時就需要使用同步器提供的3個方法來進行操作:

1、getState():獲取當前同步狀態

e76c0ed622fdc7f60f6d324125bfd99e.png

2、setState():設置當前同步狀態

7678747593d2bf9c8548279d43d032b2.png

3、compareAndSetState(int expect, int update):通過CAS設置當前狀態,該方法能保證狀態設置的原子性

325b9546f6ec36da8f1e89d1a0731ec2.png

子類推薦被定義為自定義同步組件的靜態內部類,同步器自身沒有實現任何同步接口,它僅僅是定義了若干同步狀態獲取和釋放的方法來供自定義同步組件使用。同步器既可以支持獨占式獲取同步狀態(單個線程獲取鎖),也可以支持共享式獲取同步狀態(多個線程獲取到鎖),這樣就可以方便實現不同類型的同步組件(如上圖所示的可重入鎖:ReentrantLock、可重入讀寫鎖:ReentrantReadWriteLock、計數器:CountDownLatch等等)

3 同步器可重寫的方法

以下代碼為可重入鎖繼承同步器后重寫的方法:

  • protected boolean tryAcquire(int acquires):獨占式獲取同步鎖狀態,實現該方法需要查詢當前狀態并判斷同步狀態是否符合預期,然后再進行CAS設置同步狀態。
/*** 非公平鎖*/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))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}protected final boolean tryAcquire(int acquires) {// nonfairTryAcquire方法和下面的公平鎖方法除了判斷是否在隊列首位之外沒有不同return nonfairTryAcquire(acquires);}}/*** 公平鎖*/static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {acquire(1);}/*** Fair version of tryAcquire.  Don't grant access unless* recursive call or no waiters or is first.*/protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();// 獲取當前同步狀態int c = getState();// 判斷是否符合預期if (c == 0) {// 判斷是否在隊列首位并且CAS設置當前狀態成功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;}}
  • protected boolean tryRelease(int acquires):獨占式釋放同步鎖狀態,等待獲取同步狀態的線程將有機會獲取同步狀態。
protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}
  • protected boolean isHeldExclusively():當前同步器是否在獨占模式下被線程占用,一般該方法表示是否被當前線程鎖獨占。
protected final boolean isHeldExclusively() {// While we must in general read state before owner,// we don't need to do so to check if current thread is ownerreturn getExclusiveOwnerThread() == Thread.currentThread();}

以下代碼為讀寫鎖繼承同步器后重寫的方法:

  • protected int tryAcquireShared(int arg):共享式獲取同步狀態,返回大于0的值,表示獲取鎖成功,反之獲取鎖失敗。
protected final int tryAcquireShared(int unused) {Thread current = Thread.currentThread();// 獲取當前同步狀態int c = getState();// 如果有線程持有寫鎖,則返回-1if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)return -1;// 獲取持有鎖的線程數int r = sharedCount(c);// 判斷是否阻塞,判斷線程數量,判斷CAS設置是否成功if (!readerShouldBlock() &&r < MAX_COUNT &&compareAndSetState(c, c + SHARED_UNIT)) {// 當前線程是第一個獲取到鎖的線程if (r == 0) {firstReader = current;firstReaderHoldCount = 1;// 否則就++} else if (firstReader == current) {firstReaderHoldCount++;} else {  HoldCounter rh = cachedHoldCounter;if (rh == null || rh.tid != getThreadId(current))cachedHoldCounter = rh = readHolds.get();else if (rh.count == 0)readHolds.set(rh);rh.count++;}return 1;}// 死循環去獲取鎖return fullTryAcquireShared(current);}
  • protected boolean tryReleaseShared(int arg):共享式釋放同步狀態。
protected final boolean tryReleaseShared(int unused) {Thread current = Thread.currentThread();// 對應上面獲取鎖來讀就好了if (firstReader == current) {// assert firstReaderHoldCount > 0;if (firstReaderHoldCount == 1)firstReader = null;elsefirstReaderHoldCount--;} else {HoldCounter rh = cachedHoldCounter;if (rh == null || rh.tid != getThreadId(current))rh = readHolds.get();int count = rh.count;if (count <= 1) {readHolds.remove();if (count <= 0)throw unmatchedUnlockException();}--rh.count;}for (;;) {int c = getState();int nextc = c - SHARED_UNIT;if (compareAndSetState(c, nextc))// Releasing the read lock has no effect on readers,// but it may allow waiting writers to proceed if// both read and write locks are now free.return nextc == 0;}}

上面例子,因為讀寫鎖是共享鎖,可重入鎖是獨占鎖,而同步器對于共享鎖和獨占鎖都提供了可重寫的方法來獲取鎖或者釋放鎖,所以分了兩個例子來寫。

4 同步器提供的模版方法

  • void acquire(int arg):獨占式獲取同步狀態,如果當前線程獲取同步狀態成功則返回,否則,將會進入同步隊列等待。
public final void acquire(int arg) {// 如果獲取鎖失敗,則加入同步隊列,如果加入同步隊列成功則自旋阻塞喚醒來不斷的嘗試獲取鎖,直到線程被中斷或獲取到鎖if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
  • void acquireInterruptibly(int arg):與上面acquire相似,但是當前方法如果在獲取鎖的過程中線程中斷會拋出InterruptedException并返回。
public final void acquireInterruptibly(int arg) throws InterruptedException {// 線程中斷拋出異常if (Thread.interrupted())throw new InterruptedException();// 如果沒有獲取到鎖if (!tryAcquire(arg))// 不斷自旋嘗試獲取鎖doAcquireInterruptibly(arg);}
  • boolean tryAcquireNanos(int arg, long nanosTimeout):在acquireInterruptibly()方法的基礎上增加了超時時間,如果在超時時間內獲取到了鎖,則返回true,否則返回false。
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {// 中斷拋異常if (Thread.interrupted())throw new InterruptedException();// 相應時間內不斷獲取鎖,超時返回falsereturn tryAcquire(arg) ||doAcquireNanos(arg, nanosTimeout);}
  • void acquireShared(int arg):共享式的獲取同步狀態,如果當前線程未獲取到同步狀態,則會進入同步隊列等待,與獨占鎖的主要區別是在同一時刻可以有多少個線程獲取到同步狀態。
public final void acquireShared(int arg) {// 如果獲取鎖失敗,則不斷自旋嘗試獲取鎖,tryAcquireShared方法在上面有講if (tryAcquireShared(arg) < 0)doAcquireShared(arg);}
  • void acquireSharedInterruptibly(int arg):與acquireShared方法相似,只是如果線程中斷,當前方法會拋出異常。
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {// 中斷拋出異常if (Thread.interrupted())throw new InterruptedException();// 如果獲取鎖失敗,則不斷自旋嘗試獲取鎖if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}
  • boolean tryAcquireSharedNanos(int arg, long nanosTimeout):在acquireSharedInterruptibly方法的基礎上增加了超時時間。
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {// 中斷拋出異常if (Thread.interrupted())throw new InterruptedException();// 在超時時間內如果獲取鎖失敗,則不斷自旋嘗試獲取鎖return tryAcquireShared(arg) >= 0 ||doAcquireSharedNanos(arg, nanosTimeout);}
  • boolean release(int arg):獨占式釋放同步狀態,該方法在釋放同步狀態之后,會將隊列中的第一個節點包含的線程喚醒。
public final boolean release(int arg) {// 如果獲取鎖成功if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)// 喚醒第一個節點的線程unparkSuccessor(h);return true;}return false;}
  • boolean releaseShared(int arg):共享式釋放同步狀態。
public final boolean releaseShared(int arg) {// 如果釋放鎖成功if (tryReleaseShared(arg)) {// 喚醒線程doReleaseShared();return true;}return false;}
  • Collection getQueuedThreads():獲取等待在同步隊列上的線程集合。
public final Collection<Thread> getQueuedThreads() {ArrayList<Thread> list = new ArrayList<Thread>();for (Node p = tail; p != null; p = p.prev) {Thread t = p.thread;if (t != null)list.add(t);}return list;}

同步器提供的模板方法基本是分為3類:

  • 獨占式獲取與釋放同步狀態
  • 共享式獲取與釋放同步狀態
  • 查詢同步隊列中的等待線程集合

5 根據同步器自定義同步組件

上面介紹了一些AQS提供的可重寫方法和模板方法,接下來我們自定義一個獨占鎖(在同一時刻只有一個線程能獲取鎖,其他獲取鎖的線程只能處于同步隊列中,當獲取到鎖的線程釋放鎖之后,后面的線程才能夠獲取鎖)

public class ExclusiveLock implements Lock {/*** 自定義同步器*/private static class Sync extends AbstractQueuedSynchronizer{// 判斷是否處于占用狀態@Overrideprotected boolean isHeldExclusively(){return getState() == 1;}// 加鎖@Overrideprotected boolean tryAcquire(int arg) {// 通過CAS設置同步狀態(設置成功返回true 設置失敗返回false)if (compareAndSetState(0, 1)){setExclusiveOwnerThread(Thread.currentThread());return true;}return false;}// 釋放鎖@SneakyThrows@Overrideprotected boolean tryRelease(int arg) {// 如果同步狀態為未獲取鎖,則拋出異常,沒有線程獲取到鎖,不能釋放鎖if (getState() == 0){throw new IllegalAccessException();}// 釋放鎖setExclusiveOwnerThread(null);setState(0);return true;}// 返回一個Condition,每個Condition都包含一個Condition隊列protected Condition newCondition() {return new ConditionObject();}}private final Sync sync = new Sync();@Overridepublic void lock() {// 獨占式加鎖sync.acquire(1);}@Overridepublic void lockInterruptibly() throws InterruptedException {// 加鎖線程中斷拋出異常,否則自旋加鎖sync.acquireInterruptibly(1);}@Overridepublic boolean tryLock() {// 加鎖成功返回true,否則設置占用排它鎖的線程是當前線程return sync.tryAcquire(1);}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {// 加鎖線程中斷拋出異常,否則在有效時間內嘗試自旋加鎖return sync.tryAcquireSharedNanos(1, unit.toNanos(time));}@Overridepublic void unlock() {// 釋放鎖sync.release(1);}@Overridepublic Condition newCondition() {// 返回Conditionreturn sync.newCondition();}
}

如上代碼,我們自定義了一個獨占鎖,它在同一時刻只允許一個線程占有鎖。sync內部類繼承了同步器并實現了獨占式獲取和釋放同步狀態。在tryAcquire(int arg)方法中,如果通過CAS設置成功,則代表獲取了同步狀態,而在tryRelease(int arg)方法中只是將同步狀態重制為0。用戶在使用ExclusiveLock時并不會直接和內部同步器打交道,而是調用ExclusiveLock提供的方法即可,如加鎖調用lock()方法,如果獲取鎖失敗則會被加入同步隊列中,釋放鎖調用unlock()方法,如果沒有線程獲取鎖的時候釋放鎖會拋出異常,還可以按指定時間嘗試獲取鎖等等。

結尾

本來想把同步器實現原理也寫一些的,結果看了一下篇幅好想有些許長,那就分兩篇來寫把,如果看完感覺有幫助的,請幫忙點個贊,謝謝各位,有緣下篇文章再見!

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

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

相關文章

瀏覽器里面看到的表單數據映射到python_python爬蟲入門01:教你在 Chrome 瀏覽器輕松抓包...

通過python爬蟲入門&#xff1a;什么是爬蟲&#xff0c;怎么玩爬蟲&#xff1f;我們知道了什么是爬蟲也知道了爬蟲的具體流程那么在我們要對某個網站進行爬取的時候要對其數據進行分析就要知道應該怎么請求就要知道獲取的數據是什么樣的所以我們要學會怎么抓咪咪&#xff01;哦…

sql查詢mysql參數配置_查詢參數配置

示例請求示例http(s)://rds.aliyuncs.com/?ActionDescribeParameters&DBInstanceIdrm-uf6wjk5xxxxxxx&正常返回示例XML 格式此選項設置服務器范圍內的默認填充因子值。提供填充因子是為了優化索引數據存儲和性能。fill factor50mssql2008r2此選項設置服務器范圍內的默…

python3類的繼承詳解_基于python3 類的屬性、方法、封裝、繼承詳解

下面小編就為大家帶來一篇基于python3 類的屬性、方法、封裝、繼承實例講解。小編覺得挺不錯的&#xff0c;現在就分享給大家&#xff0c;也給大家做個參考。一起跟隨小編過來看看吧Python 類Python中的類提供了面向對象編程的所有基本功能&#xff1a;類的繼承機制允許多個基類…

linux本地mysql與服務器同步數據_linux下指定mysql數據庫服務器主從同步的配置實例...

一、 概念&#xff1a;① 數據庫同步 (主從同步 --- 主數據庫寫的同時 往從服務器寫數據)② 數據庫同步 (主主同步 --- 兩臺數據庫服務器互相寫數據)二、 舉例數據庫服務器(A) 主數據庫 IP&#xff1a;192.168.1.134數據庫服務器(B) 主數據庫 IP&#xff1a;192.168.1.13…

c#二叉樹 取葉子節點個數_兩種類似但是原理不同的算法求二叉樹的所有葉子節點和...

技術提高是一個循序漸進的過程&#xff0c;所以我講的leetcode算法題從最簡單的level開始寫的&#xff0c;然后到中級難度&#xff0c;最后到hard難度全部完。目前我選擇C語言&#xff0c;Python和Java作為實現語言&#xff0c;因為這三種語言還是比較典型的。由于篇幅和精力有…

所有的service報紅但不報錯_從一個應用報錯來看centos系統的/tmp目錄自動清理規則...

概述分享最近應用碰到的一個奇怪bug&#xff0c;一開始以為是代碼上的問題&#xff0c;找了一段時間發現居然是因為系統的一個自動清理規則導致&#xff0c;下面一起來看看吧~一、應用報錯&#xff1a;logwire.core.exceptions.GeneralUnhandledException: 服務端未處理異常...…

python對瀏覽器的常用操作_Selenium元素的常用操作方法分析

本文實例講述了Selenium元素的常用操作方法。分享給大家供大家參考&#xff0c;具體如下&#xff1a;Selenium是一個用于Web應用程序測試的工具。Selenium測試直接運行在瀏覽器中&#xff0c;就像真正的用戶在操作一樣。支持的瀏覽器包括IE(7, 8, 9, 10, 11)&#xff0c;Mozill…

springboot中接口實例化_AngularJs中控制器的定義,實例化,作用域范圍

AngularJs中控制器的定義&#xff0c;實例化&#xff0c;作用域范圍基于AngularJS入門與進階(江榮波 著)這本書的筆記AngularJS 1.x的demoAngularJS1.x和Angular2,4,5是不一樣的兩個東西&#xff0c;構建方式&#xff0c;語法&#xff0c;都很多不同AngularJs控制器定義與實例化…

高斯核函數參數確定_高斯過程

之前看過高斯過程(GP)&#xff0c;不過當時也沒太看懂&#xff0c;最近花時間認真研究了一下&#xff0c;感覺總算是明白咋回事了&#xff0c;本文基于回歸問題解釋GP模型的思想和方法。文中的想法是自己思考總結得來&#xff0c;并不一定準確&#xff0c;也可能存在錯誤性。為…

uniapp光標自動定義到文本框_特檢自動化行吊靜力檢測方案

主要測量功能使用徠卡測量開發的Windows版數據傳輸軟件&#xff0c;通過藍牙連接徠卡DISTO&#xff0c;經過簡單的測量周期設置&#xff0c;即可實現自動化的距離檢測。測量數據還可以輸出Excel&#xff0c;甚至可以實時發送至PC運行的第三方軟件中&#xff0c;這么強大的軟件還…

安裝python時需要勾選_一體化污水處理設備安裝時需要注意事項

一體化污水處理設備用于處理生活污水和低濃度有機污水&#xff0c;它基本上采用機電完全封閉的結構&#xff0c;不需要專業人員進行管理。它方便且易于清潔&#xff0c;因此引起了很多關注。一體化污水處理設備的安裝方法通常為三種&#xff1a;地埋式&#xff0c;地上式和半地…

oracle驅動maven報錯_在Maven倉庫中添加Oracle JDBC驅動

由于Oracle授權問題&#xff0c;Maven3不提供Oracle JDBC driver&#xff0c;為了在Maven項目中應用Oracle JDBC driver,必須手動添加到本地倉庫。一.首先要得到Oracle JDBC Driver2.通過Oracle的安裝目錄獲得&#xff0c;位置在“{ORACLE_HOME}jdbclibojdbc14.jar”二.手動安裝…

python3.6程序_python3.6如何生成exe程序

PyInstaller的原理簡介PyInstaller其實就是把python解析器和你自己的腳本打包成一個可執行的文件&#xff0c;和編譯成真正的機器碼完全是兩回事&#xff0c;所以千萬不要指望成打包成一個可執行文件會提高運行效率&#xff0c;相反可能會降低運行效率&#xff0c;好處就是在運…

java切片_ java中一個極其強悍的新特性Stream詳解(非常實用)

java8中有兩個非常有名的改進&#xff0c;一個是Lambda表達式&#xff0c;一個是Stream。如果我們了解過函數式編程的話&#xff0c;都知道Stream真正把函數式編程的風格引入到了java中。這篇文章由簡入繁逐步介紹Stream。一、Stream是什么從名字來看&#xff0c;Stream就是一個…

java獲取網絡圖片_有了這50套Java畢設項目(源碼 案例),offer拿到手軟,無償分享...

簡介:又到了開學季&#xff0c;不少人都很是煩惱&#xff0c;手把手教你做Java畢設項目&#xff0c;有教程視頻源碼100套隨意選擇&#xff0c;試試手&#xff01;&#xff01;列舉其中2個系統大綱在線考試系統1&#xff0e;綜述網絡考試系統的項目背景及國內外發展現狀&#xf…

python字典的建立和輸出_字典的創建和使用

直接創建d {age: 23, name: Daniel, sex: 1}輸出結果&#xff1a;{age: 23, name: Daniel, sex: 1}dict函數# 通過序列對建立字典vaulues [(name, Daniel), (age, 23), (sex, 1)]print dict(values)# 通過關鍵字建立字典print dict(nameDaniel, age23, sex1)# 如果 dict 不傳…

python字符串出棧方法_1.Python實現字符串反轉的幾種方法

1.Python實現字符串反轉的幾種方法題目&#xff1a; 在Python環境下用盡可能多的方法反轉字符串,例如將s "abcdef"反轉成 "fedcba"第一種&#xff1a;使用字符串切片result s[::-1]第二種&#xff1a;使用列表的reverse方法l list(s)l.reverse()result …

linux將日期和日歷信息追加到文件中_Linux常用指令

常用指令 1、幫助指令 man [指令或者配置文件] help 指令 2、文件目錄類指令 1、pwd 功能:顯示當前工作目錄的絕對路徑 2、ls [選項] [目錄或者文件] 功能:列出文件名和目錄使用:ls -l 以列表的形式顯示信息ls -a …

python播放音樂同步歌詞_使用Python下載歌詞并嵌入歌曲文件中的實現代碼

使用python掃描本地音樂并下載歌詞這次這個真的是干貨哦&#xff0c;昨晚弄了半晚上&#xff0c;&#xff0c;&#xff0c;&#xff0c;從8點吃完飯就開始寫&#xff0c;一直到了快12點才弄好&#xff0c;&#xff0c;&#xff0c;新手&#xff0c;傷不起呀。。。。先簡單的說下…

ajax mysql點贊_php+mysql結合Ajax實現點贊功能完整實例

phpmysql結合Ajax實現點贊功能完整實例131415161718192021222324252627282930313233343536//判斷是否已經存在了cookiefunction checkcookie(gindex){var thiscookie goodplus gindex;var mapcookie getCookie(thiscookie)if (mapcookie!null && mapcookie!"&q…