精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

1.5w字,30圖帶你徹底掌握 AQS!

開發(fā) 架構(gòu)
AQS是一個用來構(gòu)建鎖和同步器的框架,Lock 包中的各種鎖, concurrent 包中的各種同步器都是基于 AQS 來構(gòu)建,所以理解 AQS 的實現(xiàn)原理至關(guān)重要,AQS 也是面試中區(qū)分侯選人的常見考點,我們務(wù)必要掌握,本文將用循序漸近地介紹 AQS,相信大家看完一定有收獲。

 [[346624]]

前言

AQS( AbstractQueuedSynchronizer )是一個用來構(gòu)建鎖和同步器(所謂同步,是指線程之間的通信、協(xié)作)的框架,Lock 包中的各種鎖(如常見的 ReentrantLock, ReadWriteLock), concurrent 包中的各種同步器(如 CountDownLatch, Semaphore, CyclicBarrier)都是基于 AQS 來構(gòu)建,所以理解 AQS 的實現(xiàn)原理至關(guān)重要,AQS 也是面試中區(qū)分侯選人的常見考點,我們務(wù)必要掌握,本文將用循序漸近地介紹 AQS,相信大家看完一定有收獲。文章目錄如下

  1. 鎖原理 - 信號量 vs 管程
  2. AQS 實現(xiàn)原理
  3. AQS 源碼剖析
  4. 如何利用 AQS 自定義一個互斥鎖

鎖原理 - 信號量 vs 管程

在并發(fā)編程領(lǐng)域,有兩大核心問題:互斥與同步,互斥即同一時刻只允許一個線程訪問共享資源,同步,即線程之間如何通信、協(xié)作,一般這兩大問題可以通過信號量和管程來解決。

信號量

信號量(Semaphore)是操作系統(tǒng)提供的一種進(jìn)程間常見的通信方式,主要用來協(xié)調(diào)并發(fā)程序?qū)蚕碣Y源的訪問,操作系統(tǒng)可以保證對信號量操作的原子性。它是怎么實現(xiàn)的呢。

  • 信號量由一個共享整型變量 S 和兩個原子操作 PV 組成,S 只能通過 P 和 V 操作來改變
  • P 操作:即請求資源,意味著 S 要減 1,如果 S < 0, 則表示沒有資源了,此時線程要進(jìn)入等待隊列(同步隊列)等待
  • V 操作: 即釋放資源,意味著 S 要加 1, 如果 S 小于等于 0,說明等待隊列里有線程,此時就需要喚醒線程。

示意圖如下

 

信號量機(jī)制的引入解決了進(jìn)程同步和互斥問題,但信號量的大量同步操作分散在各個進(jìn)程中不便于管理,還有可能導(dǎo)致系統(tǒng)死鎖。如:生產(chǎn)者消費者問題中將P、V顛倒可能死鎖(見文末參考鏈接),另外條件越多,需要的信號量就越多,需要更加謹(jǐn)慎地處理信號量之間的處理順序,否則很容易造成死鎖現(xiàn)象。

基于信號量給編程帶來的隱患,于是有了提出了對開發(fā)者更加友好的并發(fā)編程模型-管程

管程

Dijkstra 于 1971 年提出:把所有進(jìn)程對某一種臨界資源的同步操作都集中起來,構(gòu)成一個所謂的秘書進(jìn)程。凡要訪問該臨界資源的進(jìn)程,都需先報告秘書,由秘書來實現(xiàn)諸進(jìn)程對同一臨界資源的互斥使用,這種機(jī)制就是管程。

管程是一種在信號量機(jī)制上進(jìn)行改進(jìn)的并發(fā)編程模型,解決了信號量在臨界區(qū)的 PV 操作上配對的麻煩,把配對的 PV 操作集中在一起而形成的并發(fā)編程方法理論,極大降低了使用和理解成本。

  1. 管程由四部分組成:
  2. 管程內(nèi)部的共享變量。
  3. 管程內(nèi)部的條件變量。
  4. 管程內(nèi)部并行執(zhí)行的進(jìn)程。

對于局部與管程內(nèi)部的共享數(shù)據(jù)設(shè)置初始值的語句。

由此可見,管程就是一個對象監(jiān)視器。任何線程想要訪問該資源(共享變量),就要排隊進(jìn)入監(jiān)控范圍。進(jìn)入之后,接受檢查,不符合條件,則要繼續(xù)等待,直到被通知,然后繼續(xù)進(jìn)入監(jiān)視器。

需要注意的事,信號量和管程兩者是等價的,信號量可以實現(xiàn)管程,管程也可以實現(xiàn)信號量,只是兩者的表現(xiàn)形式不同而已,管程對開發(fā)者更加友好。

兩者的區(qū)別如下

 

管程為了解決信號量在臨界區(qū)的 PV 操作上的配對的麻煩,把配對的 PV 操作集中在一起,并且加入了條件變量的概念,使得在多條件下線程間的同步實現(xiàn)變得更加簡單。

怎么理解管程中的入口等待隊列,共享變量,條件變量等概念,有時候技術(shù)上的概念較難理解,我們可以借助生活中的場景來幫助我們理解,就以我們的就醫(yī)場景為例來簡單說明一下,正常的就醫(yī)流程如下:

  1. 病人去掛號后,去侯診室等待叫號
  2. 叫到自己時,就可以進(jìn)入就診室就診了
  3. 就診時,有兩種情況,一種是醫(yī)生很快就確定病人的病,并作出診斷,診斷完成后,就通知下一位病人進(jìn)來就診,一種是醫(yī)生無法確定病因,需要病人去做個驗血 / CT 檢查才能確定病情,于是病人就先去驗個血 / CT
  4. 病人驗完血 / 做完 CT 后,重新取號,等待叫號(進(jìn)入入口等待隊列)
  5. 病人等到自己的號,病人又重新拿著驗血 / CT 報告去找醫(yī)生就診

整個流程如下

 

那么管程是如何解決互斥和同步的呢

首先來看互斥,上文中醫(yī)生即共享資源(也即共享變量),就診室即為臨界區(qū),病人即線程,任何病人如果想要訪問臨界區(qū),必須首先獲取共享資源(即醫(yī)生),入口一次只允許一個線程經(jīng)過,在共享資源被占有的情況下,如果再有線程想占有共享資源,就需要到等待隊列去等候,等到獲取共享資源的線程釋放資源后,等待隊列中的線程就可以去競爭共享資源了,這樣就解決了互斥問題,所以本質(zhì)上管程是通過將共享資源及其對共享資源的操作(線程安全地獲取和釋放)封裝起來來保證互斥性的。

再來看同步,同步是通過文中的條件變量及其等待隊列實現(xiàn)的,同步的實現(xiàn)分兩種情況

  1. 病人進(jìn)入就診室后,無需做驗血 / CT 等操作,于是醫(yī)生診斷完成后,就會釋放共享資源(解鎖)去通知(notify,notifyAll)入口等待隊列的下一個病人,下一個病人聽到叫號后就能看醫(yī)生了。
  2. 如果病人進(jìn)入就診室后需要做驗血 / CT 等操作,會去驗血 / CT 隊列(條件隊列)排隊, 同時釋放共享變量(醫(yī)生),通知入口等待隊列的其他病人(線程)去獲取共享變量(醫(yī)生),獲得許可的線程執(zhí)行完臨界區(qū)的邏輯后會喚醒條件變量等待隊列中的線程,將它放到入口等待隊列中 ,等到其獲取共享變量(醫(yī)生)時,即可進(jìn)入入口(臨界區(qū))處理。

在 Java 里,鎖大多是依賴于管程來實現(xiàn)的,以大家熟悉的內(nèi)置鎖 synchronized 為例,它的實現(xiàn)原理如下。

 

可以看到 synchronized 鎖也是基于管程實現(xiàn)的,只不過它只有且只有一個條件變量(就是鎖對象本身)而已,這也是為什么JDK 要實現(xiàn) Lock 鎖的原因之一,Lock 支持多個條件變量。

通過這樣的類比,相信大家對管程的工作機(jī)制有了比較清晰的認(rèn)識,為啥要花這么大的力氣介紹管程呢,一來管程是解決并發(fā)問題的萬能鑰匙,二來 AQS 是基于 Java 并發(fā)包中管程的一種實現(xiàn),所以理解管程對我們理解 AQS 會大有幫助,接下來我們就來看看 AQS 是如何工作的。

AQS 實現(xiàn)原理

AQS 全稱是 AbstractQueuedSynchronizer,是一個用來構(gòu)建鎖和同步器的框架,它維護(hù)了一個共享資源 state 和一個 FIFO 的等待隊列(即上文中管程的入口等待隊列),底層利用了 CAS 機(jī)制來保證操作的原子性。

AQS 實現(xiàn)鎖的主要原理如下:

 

以實現(xiàn)獨占鎖為例(即當(dāng)前資源只能被一個線程占有),其實現(xiàn)原理如下:state 初始化 0,在多線程條件下,線程要執(zhí)行臨界區(qū)的代碼,必須首先獲取 state,某個線程獲取成功之后, state 加 1,其他線程再獲取的話由于共享資源已被占用,所以會到 FIFO 等待隊列去等待,等占有 state 的線程執(zhí)行完臨界區(qū)的代碼釋放資源( state 減 1)后,會喚醒 FIFO 中的下一個等待線程(head 中的下一個結(jié)點)去獲取 state。

state 由于是多線程共享變量,所以必須定義成 volatile,以保證 state 的可見性, 同時雖然 volatile 能保證可見性,但不能保證原子性,所以 AQS 提供了對 state 的原子操作方法,保證了線程安全。

另外 AQS 中實現(xiàn)的 FIFO 隊列(CLH 隊列)其實是雙向鏈表實現(xiàn)的,由 head, tail 節(jié)點表示,head 結(jié)點代表當(dāng)前占用的線程,其他節(jié)點由于暫時獲取不到鎖所以依次排隊等待鎖釋放。

所以我們不難明白 AQS 的如下定義:

  1. public abstract class AbstractQueuedSynchronizer 
  2.   extends AbstractOwnableSynchronizer 
  3.     implements java.io.Serializable { 
  4.     // 以下為雙向鏈表的首尾結(jié)點,代表入口等待隊列 
  5.     private transient volatile Node head; 
  6.     private transient volatile Node tail; 
  7.     // 共享變量 state 
  8.     private volatile int state; 
  9.     // cas 獲取 / 釋放 state,保證線程安全地獲取鎖 
  10.     protected final boolean compareAndSetState(int expect, int update) { 
  11.         // See below for intrinsics setup to support this 
  12.         return unsafe.compareAndSwapInt(this, stateOffset, expect, update); 
  13.     } 
  14.     // ... 
  15.  } 

AQS 源碼

剖析ReentrantLock 是我們比較常用的一種鎖,也是基于 AQS 實現(xiàn)的,所以接下來我們就來分析一下 ReentrantLock 鎖的實現(xiàn)來一探 AQS 究竟。本文將會采用圖文并茂的方式讓大家理解 AQS 的實現(xiàn)原理,大家在學(xué)習(xí)過程中,可以多類比一下上文中就診的例子,相信會有助于理解。

首先我們要知道 ReentrantLock 是獨占鎖,也有公平和非公平兩種鎖模式,什么是獨占與有共享模式,什么又是公平鎖與非公平鎖

與獨占鎖對應(yīng)的是共享鎖,這兩者有什么區(qū)別呢

獨占鎖:即其他線程只有在占有鎖的線程釋放后才能競爭鎖,有且只有一個線程能競爭成功(醫(yī)生只有一個,一次只能看一個病人)

共享鎖:即共享資源可以被多個線程同時占有,直到共享資源被占用完畢(多個醫(yī)生,可以看多個病人),常見的有讀寫鎖 ReadWriteLock, CountdownLatch,兩者的區(qū)別如下

 

什么是公平鎖與非公平鎖

還是以就醫(yī)為例,所謂公平鎖即大家取號后老老實實按照先來后到的順序在侯診室依次等待叫號,如果是非公平鎖呢,新來的病人(線程)很霸道,不取號排隊 ,直接去搶先看病,占有醫(yī)生(不一定成功)

公平鎖與非公平鎖

本文我們將會重點分析獨占,非公平模式的源碼實現(xiàn),不分析共享模式與 Condition 的實現(xiàn),因為剖析了獨占鎖的實現(xiàn),由于原理都是相似的,再分析共享與 Condition 就不難了。

首先我們先來看下 ReentrantLock 的使用方法

  1. // 1. 初始化可重入鎖 
  2. private ReentrantLock lock = new ReentrantLock(); 
  3. public void run() { 
  4.     // 加鎖 
  5.     lock.lock(); 
  6.     try { 
  7.         // 2. 執(zhí)行臨界區(qū)代碼 
  8.     } catch (InterruptedException e) { 
  9.         e.printStackTrace(); 
  10.     } finally { 
  11.         // 3. 解鎖 
  12.         lock.unlock(); 
  13.     } 

第一步是初始化可重入鎖,可以看到默認(rèn)使用的是非公平鎖機(jī)制

  1. public ReentrantLock() { 
  2.     sync = new NonfairSync(); 

當(dāng)然你也可以用如下構(gòu)造方法來指定使用公平鎖:

  1. public ReentrantLock(boolean fair) { 
  2.     sync = fair ? new FairSync() : new NonfairSync(); 

畫外音: FairSync 和 NonfairSync 是 ReentrantLock 實現(xiàn)的內(nèi)部類,分別指公平和非公平模式,ReentrantLock ReentrantLock 的加鎖(lock),解鎖(unlock)在內(nèi)部具體都是調(diào)用的 FairSync,NonfairSync 的加鎖和解鎖方法。

幾個類的關(guān)系如下:

 

我們先來剖析下非公平鎖(NonfairSync)的實現(xiàn)方式,來看上述示例代碼的第二步:加鎖,由于默認(rèn)的是非公平鎖的加鎖,所以我們來分析下非公平鎖是如何加鎖的

 

可以看到 lock 方法主要有兩步

  1. 使用 CAS 來獲取 state 資源,如果成功設(shè)置 1,代表 state 資源獲取鎖成功,此時記錄下當(dāng)前占用 state 的線程 setExclusiveOwnerThread(Thread.currentThread());
  2. 如果 CAS 設(shè)置 state 為 1 失敗(代表獲取鎖失敗),則執(zhí)行 acquire(1) 方法,這個方法是 AQS 提供的方法,如下
  1. public final void acquire(int arg) { 
  2.     if (!tryAcquire(arg) && 
  3.         acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
  4.         selfInterrupt(); 

tryAcquire 剖析

首先 調(diào)用 tryAcquire 嘗試著獲取 state,如果成功,則跳過后面的步驟。如果失敗,則執(zhí)行 acquireQueued 將線程加入 CLH 等待隊列中。

先來看下 tryAcquire 方法,這個方法是 AQS 提供的一個模板方法,最終由其 AQS 具體的實現(xiàn)類(Sync)實現(xiàn),由于執(zhí)行的是非公平鎖邏輯,執(zhí)行的代碼如下:

  1. final boolean nonfairTryAcquire(int acquires) { 
  2.     final Thread current = Thread.currentThread(); 
  3.     int c = getState(); 
  4.  
  5.     if (c == 0) { 
  6.         // 如果 c 等于0,表示此時資源是空閑的(即鎖是釋放的),再用 CAS 獲取鎖 
  7.         if (compareAndSetState(0, acquires)) { 
  8.             setExclusiveOwnerThread(current); 
  9.             return true
  10.         } 
  11.     } 
  12.     else if (current == getExclusiveOwnerThread()) { 
  13.         // 此條件表示之前已有線程獲得鎖,且此線程再一次獲得了鎖,獲取資源次數(shù)再加 1,這也映證了 ReentrantLock 為可重入鎖 
  14.         int nextc = c + acquires; 
  15.         if (nextc < 0) // overflow 
  16.             throw new Error("Maximum lock count exceeded"); 
  17.         setState(nextc); 
  18.         return true
  19.     } 
  20.     return false

此段代碼可知鎖的獲取主要分兩種情況

  1. state 為 0 時,代表鎖已經(jīng)被釋放,可以去獲取,于是使用 CAS 去重新獲取鎖資源,如果獲取成功,則代表競爭鎖成功,使用 setExclusiveOwnerThread(current) 記錄下此時占有鎖的線程,看到這里的 CAS,大家應(yīng)該不難理解為啥當(dāng)前實現(xiàn)是非公平鎖了,因為隊列中的線程與新線程都可以 CAS 獲取鎖啊,新來的線程不需要排隊
  2. 如果 state 不為 0,代表之前已有線程占有了鎖,如果此時的線程依然是之前占有鎖的線程(current == getExclusiveOwnerThread() 為 true),代表此線程再一次占有了鎖(可重入鎖),此時更新 state,記錄下鎖被占有的次數(shù)(鎖的重入次數(shù)),這里的 setState 方法不需要使用 CAS 更新,因為此時的鎖就是當(dāng)前線程占有的,其他線程沒有機(jī)會進(jìn)入這段代碼執(zhí)行。所以此時更新 state 是線程安全的。

假設(shè)當(dāng)前 state = 0,即鎖不被占用,現(xiàn)在有 T1, T2, T3 這三個線程要去競爭鎖

 

假設(shè)現(xiàn)在 T1 獲取鎖成功,則兩種情況分別為 1、 T1 首次獲取鎖成功

 

2、 T1 再次獲取鎖成功,state 再加 1,表示鎖被重入了兩次,當(dāng)前如果 T1一直申請占用鎖成功,state 會一直累加

 

acquireQueued 剖析

如果 tryAcquire(arg) 執(zhí)行失敗,代表獲取鎖失敗,則執(zhí)行 acquireQueued 方法,將線程加入 FIFO 等待隊列

  1. public final void acquire(int arg) { 
  2.     if (!tryAcquire(arg) && 
  3.         acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
  4.         selfInterrupt(); 

所以接下來我們來看看 acquireQueued 的執(zhí)行邏輯,首先會調(diào)用 addWaiter(Node.EXCLUSIVE) 將包含有當(dāng)前線程的 Node 節(jié)點入隊, Node.EXCLUSIVE 代表此結(jié)點為獨占模式

再來看下 addWaiter 是如何實現(xiàn)的

  1. private Node addWaiter(Node mode) { 
  2.     Node node = new Node(Thread.currentThread(), mode); 
  3.     Node pred = tail; 
  4.     // 如果尾結(jié)點不為空,則用 CAS 將獲取鎖失敗的線程入隊 
  5.     if (pred != null) { 
  6.         node.prev = pred; 
  7.         if (compareAndSetTail(pred, node)) { 
  8.             pred.next = node; 
  9.             return node; 
  10.         } 
  11.     } 
  12.     // 如果結(jié)點為空,執(zhí)行 enq 方法 
  13.     enq(node); 
  14.     return node; 

這段邏輯比較清楚,首先是獲取 FIFO 隊列的尾結(jié)點,如果尾結(jié)點存在,則采用 CAS 的方式將等待線程入隊,如果尾結(jié)點為空則執(zhí)行 enq 方法

  1. private Node enq(final Node node) { 
  2.     for (;;) { 
  3.         Node t = tail; 
  4.         if (t == null) { 
  5.             // 尾結(jié)點為空,說明 FIFO 隊列未初始化,所以先初始化其頭結(jié)點 
  6.             if (compareAndSetHead(new Node())) 
  7.                 tail = head; 
  8.         } else { 
  9.             // 尾結(jié)點不為空,則將等待線程入隊 
  10.             node.prev = t; 
  11.             if (compareAndSetTail(t, node)) { 
  12.                 t.next = node; 
  13.                 return t; 
  14.             } 
  15.         } 
  16.     } 

首先判斷 tail 是否為空,如果為空說明 FIFO 隊列的 head,tail 還未構(gòu)建,此時先構(gòu)建頭結(jié)點,構(gòu)建之后再用 CAS 的方式將此線程結(jié)點入隊

使用 CAS 創(chuàng)建 head 節(jié)點的時候只是簡單調(diào)用了 new Node() 方法,并不像其他節(jié)點那樣記錄 thread,這是為啥

因為 head 結(jié)點為虛結(jié)點,它只代表當(dāng)前有線程占用了 state,至于占用 state 的是哪個線程,其實是調(diào)用了上文的 setExclusiveOwnerThread(current) ,即記錄在 exclusiveOwnerThread 屬性里。

執(zhí)行完 addWaiter 后,線程入隊成功,現(xiàn)在就要看最后一個最關(guān)鍵的方法 acquireQueued 了,這個方法有點難以理解,先不急,我們先用三個線程來模擬一下之前的代碼對應(yīng)的步驟

1、假設(shè) T1 獲取鎖成功,由于此時 FIFO 未初始化,所以先創(chuàng)建 head 結(jié)點

 

2、此時 T2 或 T3 再去競爭 state 失敗,入隊,如下圖:

 

好了,現(xiàn)在問題來了, T2,T3 入隊后怎么處理,是馬上阻塞嗎,馬上阻塞意味著線程從運行態(tài)轉(zhuǎn)為阻塞態(tài) ,涉及到用戶態(tài)向內(nèi)核態(tài)的切換,而且喚醒后也要從內(nèi)核態(tài)轉(zhuǎn)為用戶態(tài),開銷相對比較大,所以 AQS 對這種入隊的線程采用的方式是讓它們自旋來競爭鎖,如下圖示

 

不過聰明的你可能發(fā)現(xiàn)了一個問題,如果當(dāng)前鎖是獨占鎖,如果鎖一直被被 T1 占有, T2,T3 一直自旋沒太大意義,反而會占用 CPU,影響性能,所以更合適的方式是它們自旋一兩次競爭不到鎖后識趣地阻塞以等待前置節(jié)點釋放鎖后再來喚醒它。

另外如果鎖在自旋過程中被中斷了,或者自旋超時了,應(yīng)該處于「取消」?fàn)顟B(tài)。

基于每個 Node 可能所處的狀態(tài),AQS 為其定義了一個變量 waitStatus,根據(jù)這個變量值對相應(yīng)節(jié)點進(jìn)行相關(guān)的操作,我們一起來看這看這個變量都有哪些值,是時候看一個 Node 結(jié)點的屬性定義了

  1. static final class Node { 
  2.     static final Node SHARED = new Node();//標(biāo)識等待節(jié)點處于共享模式 
  3.     static final Node EXCLUSIVE = null;//標(biāo)識等待節(jié)點處于獨占模式 
  4.  
  5.     static final int CANCELLED = 1; //由于超時或中斷,節(jié)點已被取消 
  6.     static final int SIGNAL = -1;  // 節(jié)點阻塞(park)必須在其前驅(qū)結(jié)點為 SIGNAL 的狀態(tài)下才能進(jìn)行,如果結(jié)點為 SIGNAL,則其釋放鎖或取消后,可以通過 unpark 喚醒下一個節(jié)點, 
  7.     static final int CONDITION = -2;//表示線程在等待條件變量(先獲取鎖,加入到條件等待隊列,然后釋放鎖,等待條件變量滿足條件;只有重新獲取鎖之后才能返回) 
  8.     static final int PROPAGATE = -3;//表示后續(xù)結(jié)點會傳播喚醒的操作,共享模式下起作用 
  9.  
  10.     //等待狀態(tài):對于condition節(jié)點,初始化為CONDITION;其它情況,默認(rèn)為0,通過CAS操作原子更新 
  11.     volatile int waitStatus; 

通過狀態(tài)的定義,我們可以猜測一下 AQS 對線程自旋的處理:如果當(dāng)前節(jié)點的上一個節(jié)點不為 head,且它的狀態(tài)為 SIGNAL,則結(jié)點進(jìn)入阻塞狀態(tài)。來看下代碼以映證我們的猜測:

  1. final boolean acquireQueued(final Node node, int arg) { 
  2.     boolean failed = true
  3.     try { 
  4.         boolean interrupted = false
  5.         for (;;) { 
  6.             final Node p = node.predecessor(); 
  7.             // 如果前一個節(jié)點是 head,則嘗試自旋獲取鎖 
  8.             if (p == head && tryAcquire(arg)) { 
  9.                 //  將 head 結(jié)點指向當(dāng)前節(jié)點,原 head 結(jié)點出隊 
  10.                 setHead(node); 
  11.                 p.next = null; // help GC 
  12.                 failed = false
  13.                 return interrupted; 
  14.             } 
  15.             // 如果前一個節(jié)點不是 head 或者競爭鎖失敗,則進(jìn)入阻塞狀態(tài) 
  16.             if (shouldParkAfterFailedAcquire(p, node) && 
  17.                 parkAndCheckInterrupt()) 
  18.                 interrupted = true
  19.         } 
  20.     } finally { 
  21.         if (failed) 
  22.             // 如果線程自旋中因為異常等原因獲取鎖最終失敗,則調(diào)用此方法 
  23.             cancelAcquire(node); 
  24.     } 

先來看第一種情況,如果當(dāng)前結(jié)點的前一個節(jié)點是 head 結(jié)點,且獲取鎖(tryAcquire)成功的處理

 

可以看到主要的處理就是把 head 指向當(dāng)前節(jié)點,并且讓原 head 結(jié)點出隊,這樣由于原 head 不可達(dá), 會被垃圾回收。

注意其中 setHead 的處理

  1. private void setHead(Node node) { 
  2.     head = node; 
  3.     node.thread = null
  4.     node.prev = null

將 head 設(shè)置成當(dāng)前結(jié)點后,要把節(jié)點的 thread, pre 設(shè)置成 null,因為之前分析過了,head 是虛節(jié)點,不保存除 waitStatus(結(jié)點狀態(tài))的其他信息,所以這里把 thread ,pre 置為空,因為占有鎖的線程由 exclusiveThread 記錄了,如果 head 再記錄 thread 不僅多此一舉,反而在釋放鎖的時候要多操作一遍 head 的 thread 釋放。

如果前一個節(jié)點不是 head 或者競爭鎖失敗,則首先調(diào)用 shouldParkAfterFailedAcquire 方法判斷鎖是否應(yīng)該停止自旋進(jìn)入阻塞狀態(tài):

  1. private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { 
  2.     int ws = pred.waitStatus; 
  3.          
  4.     if (ws == Node.SIGNAL) 
  5.        // 1. 如果前置頂點的狀態(tài)為 SIGNAL,表示當(dāng)前節(jié)點可以阻塞了 
  6.         return true
  7.     if (ws > 0) { 
  8.         // 2. 移除取消狀態(tài)的結(jié)點 
  9.         do { 
  10.             node.prev = pred = pred.prev; 
  11.         } while (pred.waitStatus > 0); 
  12.         pred.next = node; 
  13.     } else { 
  14.         // 3. 如果前置節(jié)點的 ws 不為 0,則其設(shè)置為 SIGNAL, 
  15.         compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 
  16.     } 
  17.     return false

這一段代碼有點繞,需要稍微動點腦子,按以上步驟一步步來看

1、 首先我們要明白,根據(jù)之前 Node 類的注釋,如果前驅(qū)節(jié)點為 SIGNAL,則當(dāng)前節(jié)點可以進(jìn)入阻塞狀態(tài)。

如圖示:T2,T3 的前驅(qū)節(jié)點的 waitStatus 都為 SIGNAL,所以 T2,T3 此時都可以阻塞。

 

2、如果前驅(qū)節(jié)點為取消狀態(tài),則前驅(qū)節(jié)點需要移除,這些采用了一個更巧妙的方法,把所有當(dāng)前節(jié)點之前所有 waitStatus 為取消狀態(tài)的節(jié)點全部移除,假設(shè)有四個線程如下,T2,T3 為取消狀態(tài),則執(zhí)行邏輯后如下圖所示,T2, T3 節(jié)點會被 GC。

 

3、如果前驅(qū)節(jié)點小于等于 0,則需要首先將其前驅(qū)節(jié)點置為 SIGNAL,因為前文我們分析過,當(dāng)前節(jié)點進(jìn)入阻塞的一個條件是前驅(qū)節(jié)點必須為 SIGNAL,這樣下一次自旋后發(fā)現(xiàn)前驅(qū)節(jié)點為 SIGNAL,就會返回 true(即步驟 1)

shouldParkAfterFailedAcquire 返回 true 代表線程可以進(jìn)入阻塞中斷,那么下一步 parkAndCheckInterrupt 就該讓線程阻塞了

  1. private final boolean parkAndCheckInterrupt() { 
  2.     // 阻塞線程 
  3.     LockSupport.park(this); 
  4.     // 返回線程是否中斷過,并且清除中斷狀態(tài)(在獲得鎖后會補一次中斷) 
  5.     return Thread.interrupted(); 

這里的阻塞線程很容易理解,但為啥要判斷線程是否中斷過呢,因為如果線程在阻塞期間收到了中斷,喚醒(轉(zhuǎn)為運行態(tài))獲取鎖后(acquireQueued 為 true)需要補一個中斷,如下所示:

  1. public final void acquire(int arg) { 
  2.     if (!tryAcquire(arg) && 
  3.         acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
  4.         // 如果是因為中斷喚醒的線程,獲取鎖后需要補一下中斷 
  5.         selfInterrupt(); 

至此,獲取鎖的流程已經(jīng)分析完畢,不過還有一個疑惑我們還沒解開:前文不是說 Node 狀態(tài)為取消狀態(tài)會被取消嗎,那 Node 什么時候會被設(shè)置為取消狀態(tài)呢。

回頭看 acquireQueued

  1. final boolean acquireQueued(final Node node, int arg) { 
  2.     boolean failed = true
  3.     try { 
  4.         // 省略自旋獲取鎖代碼         
  5.     } finally { 
  6.         if (failed) 
  7.             // 如果線程自旋中因為異常等原因獲取鎖最終失敗,則調(diào)用此方法 
  8.             cancelAcquire(node); 
  9.     } 

看最后一個 cancelAcquire 方法,如果線程自旋中因為異常等原因獲取鎖最終失敗,則會調(diào)用此方法

  1. private void cancelAcquire(Node node) { 
  2.     // 如果節(jié)點為空,直接返回 
  3.     if (node == null
  4.         return
  5.     // 由于線程要被取消了,所以將 thread 線程清掉 
  6.     node.thread = null
  7.  
  8.     // 下面這步表示將 node 的 pre 指向之前第一個非取消狀態(tài)的結(jié)點(即跳過所有取消狀態(tài)的結(jié)點),waitStatus > 0 表示當(dāng)前結(jié)點狀態(tài)為取消狀態(tài) 
  9.     Node pred = node.prev; 
  10.     while (pred.waitStatus > 0) 
  11.         node.prev = pred = pred.prev; 
  12.  
  13.     // 獲取經(jīng)過過濾后的 pre 的 next 結(jié)點,這一步主要用在后面的 CAS 設(shè)置 pre 的 next 節(jié)點上 
  14.     Node predNext = pred.next
  15.     // 將當(dāng)前結(jié)點設(shè)置為取消狀態(tài) 
  16.     node.waitStatus = Node.CANCELLED; 
  17.  
  18.     // 如果當(dāng)前取消結(jié)點為尾結(jié)點,使用 CAS 則將尾結(jié)點設(shè)置為其前驅(qū)節(jié)點,如果設(shè)置成功,則尾結(jié)點的 next 指針設(shè)置為空 
  19.     if (node == tail && compareAndSetTail(node, pred)) { 
  20.         compareAndSetNext(pred, predNext, null); 
  21.     } else { 
  22.     // 這一步看得有點繞,我們想想,如果當(dāng)前節(jié)點取消了,那是不是要把當(dāng)前節(jié)點的前驅(qū)節(jié)點指向當(dāng)前節(jié)點的后繼節(jié)點,但是我們之前也說了,要喚醒或阻塞結(jié)點,須在其前驅(qū)節(jié)點的狀態(tài)為 SIGNAL 的條件才能操作,所以在設(shè)置 pre 的 next 節(jié)點時要保證 pre 結(jié)點的狀態(tài)為 SIGNAL,想通了這一點相信你不難理解以下代碼。 
  23.         int ws; 
  24.         if (pred != head && 
  25.             ((ws = pred.waitStatus) == Node.SIGNAL || 
  26.              (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && 
  27.             pred.thread != null) { 
  28.             Node next = node.next
  29.             if (next != null && next.waitStatus <= 0) 
  30.                 compareAndSetNext(pred, predNext, next); 
  31.         } else { 
  32.         // 如果 pre 為 head,或者  pre 的狀態(tài)設(shè)置 SIGNAL 失敗,則直接喚醒后繼結(jié)點去競爭鎖,之前我們說過, SIGNAL 的結(jié)點取消(或釋放鎖)后可以喚醒后繼結(jié)點 
  33.             unparkSuccessor(node); 
  34.         } 
  35.         node.next = node; // help GC 
  36.     } 

這一段代碼有點繞,我們一個個來看,首先考慮以下情況

1、首先第一步當(dāng)前節(jié)點之前有取消結(jié)點時,則邏輯如下

 

2、如果當(dāng)前結(jié)點既非頭結(jié)點的后繼結(jié)點,也非尾結(jié)點,即步驟 1 所示,則最終結(jié)果如下

 

這里肯定有人問,這種情況下當(dāng)前節(jié)點與它的前驅(qū)結(jié)點無法被 GC 啊,還記得我們上文分析鎖自旋時的處理嗎,再看下以下代碼

  1. private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { 
  2.     int ws = pred.waitStatus; 
  3.     // 省略無關(guān)代碼 
  4.     if (ws > 0) { 
  5.         // 移除取消狀態(tài)的結(jié)點 
  6.         do { 
  7.             node.prev = pred = pred.prev; 
  8.         } while (pred.waitStatus > 0); 
  9.         pred.next = node; 
  10.     }  
  11.     return false

這段代碼會將 node 的 pre 指向之前 waitStatus 為非 CANCEL 的節(jié)點

所以當(dāng) T4 執(zhí)行這段代碼時,會變成如下情況

可以看到此時中間的兩個 CANCEL 節(jié)點不可達(dá)了,會被 GC

3、如果當(dāng)前結(jié)點為 tail 結(jié)點,則結(jié)果如下,這種情況下當(dāng)前結(jié)點不可達(dá),會被 GC

 

4、如果當(dāng)前結(jié)點為 head 的后繼結(jié)點時,如下

 

結(jié)果中的 CANCEL 結(jié)點同樣會在 tail 結(jié)點自旋調(diào)用 shouldParkAfterFailedAcquire 后不可達(dá),如下

 

自此我們終于分析完了鎖的獲取流程,接下來我們來看看鎖是如何釋放的。

鎖釋放

不管是公平鎖還是非公平鎖,最終都是調(diào)的 AQS 的如下模板方法來釋放鎖

  1. // java.util.concurrent.locks.AbstractQueuedSynchronizer 
  2.  
  3. public final boolean release(int arg) { 
  4.     // 鎖釋放是否成功 
  5.     if (tryRelease(arg)) { 
  6.         Node h = head; 
  7.         if (h != null && h.waitStatus != 0) 
  8.             unparkSuccessor(h); 
  9.         return true
  10.     } 
  11.     return false

tryRelease 方法定義在了 AQS 的子類 Sync 方法里

  1. // java.util.concurrent.locks.ReentrantLock.Sync 
  2.  
  3. protected final boolean tryRelease(int releases) { 
  4.     int c = getState() - releases; 
  5.     // 只有持有鎖的線程才能釋放鎖,所以如果當(dāng)前鎖不是持有鎖的線程,則拋異常 
  6.     if (Thread.currentThread() != getExclusiveOwnerThread()) 
  7.         throw new IllegalMonitorStateException(); 
  8.     boolean free = false
  9.     // 說明線程持有的鎖全部釋放了,需要釋放 exclusiveOwnerThread 的持有線程 
  10.     if (c == 0) { 
  11.         free = true
  12.         setExclusiveOwnerThread(null); 
  13.     } 
  14.     setState(c); 
  15.     return free

鎖釋放成功后該干嘛,顯然是喚醒之后 head 之后節(jié)點,讓它來競爭鎖

  1. // java.util.concurrent.locks.AbstractQueuedSynchronizer 
  2.  
  3. public final boolean release(int arg) { 
  4.     // 鎖釋放是否成功 
  5.     if (tryRelease(arg)) { 
  6.         Node h = head; 
  7.         if (h != null && h.waitStatus != 0) 
  8.             // 鎖釋放成功后,喚醒 head 之后的節(jié)點,讓它來競爭鎖 
  9.             unparkSuccessor(h); 
  10.         return true
  11.     } 
  12.     return false

這里釋放鎖的條件為啥是 h != null && h.waitStatus != 0 呢。

如果 h == null, 這有兩種可能,一種是一個線程在競爭鎖,現(xiàn)在它釋放了,當(dāng)然沒有所謂的喚醒后繼節(jié)點,一種是其他線程正在運行競爭鎖,只是還未初始化頭節(jié)點,既然其他線程正在運行,也就無需執(zhí)行喚醒操作

如果 h != null 且 h.waitStatus == 0,說明 head 的后繼節(jié)點正在自旋競爭鎖,也就是說線程是運行狀態(tài)的,無需喚醒。

如果 h != null 且 h.waitStatus < 0, 此時 waitStatus 值可能為 SIGNAL,或 PROPAGATE,這兩種情況說明后繼結(jié)點阻塞需要被喚醒

來看一下喚醒方法 unparkSuccessor:

  1. private void unparkSuccessor(Node node) { 
  2.     // 獲取 head 的 waitStatus(假設(shè)其為 SIGNAL),并用 CAS 將其置為 0,為啥要做這一步呢,之前我們分析過多次,其實 waitStatus = SIGNAL(< -1)或 PROPAGATE(-·3) 只是一個標(biāo)志,代表在此狀態(tài)下,后繼節(jié)點可以喚醒,既然正在喚醒后繼節(jié)點,自然可以將其重置為 0,當(dāng)然如果失敗了也不影響其喚醒后繼結(jié)點 
  3.     int ws = node.waitStatus; 
  4.     if (ws < 0) 
  5.         compareAndSetWaitStatus(node, ws, 0); 
  6.  
  7.     // 以下操作為獲取隊列第一個非取消狀態(tài)的結(jié)點,并將其喚醒 
  8.     Node s = node.next
  9.     // s 狀態(tài)為非空,或者其為取消狀態(tài),說明 s 是無效節(jié)點,此時需要執(zhí)行 if 里的邏輯 
  10.     if (s == null || s.waitStatus > 0) { 
  11.         s = null
  12.         // 以下操作為從尾向前獲取最后一個非取消狀態(tài)的結(jié)點 
  13.         for (Node t = tail; t != null && t != node; t = t.prev) 
  14.             if (t.waitStatus <= 0) 
  15.                 s = t; 
  16.     } 
  17.     if (s != null
  18.         LockSupport.unpark(s.thread); 

這里的尋找隊列的第一個非取消狀態(tài)的節(jié)點為啥要從后往前找呢,因為節(jié)點入隊并不是原子操作,如下

 

線程自旋時時是先執(zhí)行 node.pre = pred, 然后再執(zhí)行 pred.next = node,如果 unparkSuccessor 剛好在這兩者之間執(zhí)行,此時是找不到 head 的后繼節(jié)點的,如下

 

如何利用 AQS 自定義一個互斥鎖

AQS 通過提供 state 及 FIFO 隊列的管理,為我們提供了一套通用的實現(xiàn)鎖的底層方法,基本上定義一個鎖,都是轉(zhuǎn)為在其內(nèi)部定義 AQS 的子類,調(diào)用 AQS 的底層方法來實現(xiàn)的,由于 AQS 在底層已經(jīng)為了定義好了這些獲取 state 及 FIFO 隊列的管理工作,我們要實現(xiàn)一個鎖就比較簡單了,我們可以基于 AQS 來實現(xiàn)一個非可重入的互斥鎖,如下所示

  1. public class Mutex  { 
  2.  
  3.     private Sync sync = new Sync(); 
  4.      
  5.     public void lock () { 
  6.         sync.acquire(1); 
  7.     } 
  8.      
  9.     public void unlock () { 
  10.         sync.release(1); 
  11.     } 
  12.  
  13.     private static class Sync extends AbstractQueuedSynchronizer { 
  14.         @Override 
  15.         protected boolean tryAcquire (int arg) { 
  16.             return compareAndSetState(0, 1); 
  17.         } 
  18.  
  19.         @Override 
  20.         protected boolean tryRelease (int arg) { 
  21.             setState(0); 
  22.             return true
  23.         } 
  24.  
  25.         @Override 
  26.         protected boolean isHeldExclusively () { 
  27.             return getState() == 1; 
  28.         } 
  29.     } 

可以看到區(qū)區(qū)幾行代碼就實現(xiàn)了,確實很方便。

總結(jié)本文通過圖文并茂的方式幫助大家梳理了一遍 AQS 的實現(xiàn)方式,相信大家看完對 AQS 應(yīng)該有了比較深入的認(rèn)識,首先要明白鎖的實現(xiàn)原理,信號量及管程,理解了管程的設(shè)計思路對 AQS 有了一個概念上的認(rèn)識,再去讀源碼就會用管程的概念去套,也就更容易理解了,另外大家可以多類比一下生活中的場景,如就醫(yī)場景,通過類似的方式學(xué)習(xí)能讓我們更好地理解相關(guān)技術(shù)的設(shè)計思路。

本文轉(zhuǎn)載自微信公眾號「碼海」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系碼海公眾號。

 

責(zé)任編輯:武曉燕 來源: 碼海
相關(guān)推薦

2021-01-09 13:57:05

阻塞隊列并發(fā)

2021-05-18 06:55:07

Java AQS源碼

2024-06-21 09:27:05

2011-11-08 10:08:04

ARM服務(wù)器處理器Calxeda

2021-01-22 17:57:31

SQL數(shù)據(jù)庫函數(shù)

2023-12-15 09:45:21

阻塞接口

2022-02-28 11:10:42

ZGCG1收集器

2023-05-23 22:19:04

索引MySQL優(yōu)化

2024-09-09 23:15:55

2019-11-04 13:51:13

機(jī)器學(xué)習(xí)人工智能計算機(jī)

2022-07-11 11:06:11

RocketMQ函數(shù).消費端

2020-09-12 16:45:49

Git

2022-07-04 11:06:02

RocketMQ事務(wù)消息實現(xiàn)

2021-08-11 22:17:48

負(fù)載均衡LVS機(jī)制

2021-10-22 09:28:15

開發(fā)技能代碼

2022-12-26 08:36:24

JavaMESA模型

2018-03-09 14:59:02

F5應(yīng)用交付

2021-07-24 11:15:19

開發(fā)技能代碼

2021-12-06 07:15:47

Pulsar地域復(fù)制

2023-12-04 08:10:34

Spring循環(huán)依賴
點贊
收藏

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

免费在线超碰| 三级视频在线观看| 日韩精品视频中文字幕| 亚洲电影在线播放| 欧美日韩亚洲综合一区二区三区激情在线| www.国产毛片| 91tv精品福利国产在线观看| 亚洲成人激情在线观看| 亚洲五月天综合| 日本不卡影院| 久久久不卡网国产精品二区| 91传媒免费看| 日本黄色一级视频| 最新国产精品| 国产一区二区三区在线| 少妇熟女视频一区二区三区 | 国产精品国产三级国产专播品爱网| 成人免费在线网址| 欧美三级一区二区三区| 在线电影一区二区| 一区二区欧美久久| 稀缺小u女呦精品呦| 青青青国产精品| 欧美日韩在线一区| 国产制服91一区二区三区制服| 欧美偷拍视频| 成人美女在线视频| 91在线视频九色| 日韩精品成人免费观看视频| 亚洲精选成人| 欧美激情亚洲国产| 777777国产7777777| 精品一区二区三区的国产在线观看| 精品伦理精品一区| 欧美一级免费在线| 福利精品在线| 欧美在线免费观看视频| 美女福利视频在线| www欧美xxxx| 一区二区在线观看免费视频播放| 亚洲精品视频一区二区三区| 美女毛片在线看| 99国产精品视频免费观看| 91|九色|视频| 亚洲AV无码一区二区三区性| 国产在线视频一区二区三区| 国产精品午夜国产小视频| 999视频在线| 玖玖国产精品视频| 欧美专区中文字幕| 日韩不卡在线播放| 国产精品尤物| 日本成人精品在线| 波多野结衣人妻| 久久在线91| 国产91九色视频| 男人天堂2024| 日日夜夜一区二区| 国产激情综合五月久久| 奴色虐av一区二区三区| 久久狠狠一本精品综合网| 欧美自拍视频在线| 狠狠狠狠狠狠狠| 日韩不卡免费视频| 国产精品自产拍在线观看中文 | 日本怡春院一区二区| 国产91在线播放精品91| 在线观看亚洲黄色| 久久激情五月婷婷| 91免费观看网站| 精品久久人妻av中文字幕| 国产成人在线网站| 精品乱码一区| 成人在线二区| 国产精品的网站| 日本中文字幕一级片| 国产精品探花在线| 一本到三区不卡视频| 九九热免费精品视频| 国产免费av国片精品草莓男男| 日韩一区和二区| 色哟哟视频在线| 九一亚洲精品| 久久久精品免费| 日本一区二区欧美| 日韩av电影免费观看高清完整版| 91精品国产综合久久久久久久久| 成人av无码一区二区三区| www.一区二区| 宅男一区二区三区| av最新在线| 欧美色窝79yyyycom| а 天堂 在线| 亚洲v天堂v手机在线| www.欧美精品| 国产无套内射又大又猛又粗又爽| 久热国产精品| 不卡视频一区| 免费在线视频一级不卡| 亚洲欧美日韩在线| 岳毛多又紧做起爽| 国产精品亚洲四区在线观看| 日韩黄在线观看| 五月综合色婷婷| 国产欧美日韩一级| 亚洲va电影大全| 欧美日本韩国一区二区| 亚洲精品免费在线| 国产自偷自偷免费一区| 国产成人aa在线观看网站站| yw.139尤物在线精品视频| 国产精品2020| 国产一区欧美一区| 日韩欧美精品一区二区| 91jq激情在线观看| 9191成人精品久久| 国产黄色大片免费看| 亚洲国产二区| 91黄色精品| a黄色在线观看| 狠狠做深爱婷婷久久综合一区| 91精产国品一二三产区别沈先生| 九九亚洲视频| 欧美一级淫片aaaaaaa视频| 精品久久人妻av中文字幕| 日韩一区中文字幕| 国产超碰在线播放| 九热爱视频精品视频| 97视频网站入口| 亚洲精品国偷拍自产在线观看蜜桃| 中文字幕精品一区二区精品绿巨人 | 国产欧美韩国高清| 精品美女视频在线观看免费软件 | 999久久久久久| 国产精品久久久久四虎| 日本一极黄色片| 任你躁在线精品免费| 欧美激情一区二区三区在线视频观看| 国产日产亚洲系列最新| 中文字幕一区二区三| 五月婷婷丁香色| 久久免费精品视频在这里| 国产精品91在线观看| 国产中文字幕在线播放| 欧美性猛交xxxxx免费看| 91黄色免费视频| 亚洲精品乱码| 久久婷婷开心| 免费在线小视频| 亚洲精品天天看| 香蕉影院在线观看| 日本一区二区视频在线| 欧美两根一起进3p做受视频| 国产日韩欧美一区二区三区| 国产极品jizzhd欧美| www.亚洲资源| 91精品久久久久久久久99蜜臂| 人人干在线观看| 黄色精品一二区| 99久re热视频精品98| 亚洲精品黑牛一区二区三区| 欧美激情精品久久久久久蜜臀| 亚洲经典一区二区| 精品久久久久久久久久国产| 欧美图片一区二区| 奇米综合一区二区三区精品视频| 亚洲日本无吗高清不卡| 国产日韩欧美中文在线| 欧美老女人性视频| 色噜噜一区二区三区| 黑人巨大精品欧美一区二区免费| 波多野结衣a v在线| 蜜桃视频一区二区三区 | 国产精品亚洲欧美| 日本在线播放一区| 精品国产18久久久久久二百| 97久久超碰福利国产精品…| 极品白浆推特女神在线观看| 欧美日韩国产精品自在自线| 欧美成人精品激情在线视频| 不卡高清视频专区| xxxx一级片| 雨宫琴音一区二区三区| 快播亚洲色图| 国产精品日本一区二区三区在线| 国内揄拍国内精品| 成人动漫在线播放| 欧美第一区第二区| 亚洲第一精品在线观看| 亚洲欧洲成人精品av97| 精品国产一区在线| 久久精品理论片| 男人日女人视频网站| 欧美精品一区二区三区精品| 91视频在线免费观看| 亚洲综合电影| 欧美日本亚洲视频| 国产一级网站视频在线| 日韩美女在线视频| 亚洲国产精品无码久久久| 亚洲精品国产第一综合99久久 | 国产大学生av| 视频一区中文字幕| 日韩精品一区二区免费| 成人精品影视| 久久本道综合色狠狠五月| 国产区一区二| 国产精品色午夜在线观看| 亚洲第一av| 欧美日韩电影在线观看| 中文字幕日本在线观看| 精品在线小视频| www.麻豆av| 欧美日韩免费不卡视频一区二区三区| 亚洲精品国产精品乱码| 亚洲欧美色图小说| 制服丨自拍丨欧美丨动漫丨| 久久伊人中文字幕| 国产69视频在线观看| 国产毛片精品视频| 99视频在线视频| 石原莉奈在线亚洲二区| av网站在线观看不卡| 亚洲高清自拍| 神马午夜伦理影院| 欧美电影免费| 日产精品久久久一区二区| 欧美一区自拍| 国产另类第一区| 99精品中文字幕在线不卡| 亚洲xxxx做受欧美| 国产一区二区av在线| 成人黄色免费片| 4438五月综合| 91精品国产自产在线老师啪| 国内自拍亚洲| 国产精品香蕉在线观看| 亚洲成人va| 国产精品高潮在线| 欧美亚洲大片| 国产91九色视频| 巨胸喷奶水www久久久免费动漫| 欧美专区福利在线| 九九热线视频只有这里最精品| 欧美一级大胆视频| 女生影院久久| 国产精品69精品一区二区三区| 日韩av大片站长工具| 国产成人在线一区二区| 欧美日韩国产网站| 国产精品三级久久久久久电影| 成人国产一区二区三区精品麻豆| 国产精品视频免费观看www| 欧美一级做a| 91牛牛免费视频| 久久精品九色| 国产精品一区二区三区免费观看| 动漫3d精品一区二区三区乱码| 国产伦精品一区二区三区四区视频| 欧美日韩看看2015永久免费| 久久综合九色综合久99| 国产一区二区观看| 一区二区冒白浆视频| 欧美黄色免费| 蜜桃传媒一区二区三区| 久久高清一区| 成年网站免费在线观看| 国产成人免费视频一区| 丰满大乳奶做爰ⅹxx视频 | 日本一区二区三区视频在线观看| 国产剧情在线观看一区| 在线观看成人一级片| 狠狠入ady亚洲精品| 日韩在线视频在线观看| 另类调教123区| 99久久综合网| 久久久欧美精品sm网站| 日韩福利小视频| 亚洲第一主播视频| 奴色虐av一区二区三区| 日韩欧美的一区二区| 免费成人av电影| 九九热最新视频//这里只有精品| 九色porny丨国产首页在线| 国产精品偷伦一区二区| 日韩一二三区| 日本不卡一二三区| 欧美精品首页| 欧美精品无码一区二区三区| 激情图区综合网| 久久午夜夜伦鲁鲁片| 亚洲欧美日韩久久精品| 国产99久久久| 日韩欧美色综合| 国产视频第一区| 久久久久久综合网天天| jizzjizz少妇亚洲水多| 国产高清一区二区三区| 热久久天天拍国产| 欧美精品自拍视频| 精品一区二区综合| 久久无码人妻精品一区二区三区| 中文字幕制服丝袜一区二区三区| 国产午夜福利片| 欧美人与禽zozo性伦| 日韩资源在线| 欧美激情xxxx性bbbb| 麻豆久久久久| 日韩国产美国| 国产精品亚洲产品| 91传媒理伦片在线观看| 亚洲欧美日韩成人高清在线一区| av片免费观看| 日韩电影中文字幕在线| 午夜伦理大片视频在线观看| 国产欧美精品一区二区三区-老狼 国产欧美精品一区二区三区介绍 国产欧美精品一区二区 | 无码人妻aⅴ一区二区三区有奶水| 日韩欧美一级特黄在线播放| аⅴ资源新版在线天堂| 日本久久久a级免费| 极品一区美女高清| 97免费视频观看| 国产原创一区二区三区| 99精品中文字幕| 欧美伊人精品成人久久综合97| 三级视频在线播放| 97高清免费视频| 成人知道污网站| 久久久久久久久影视| 国内久久婷婷综合| 国产又粗又猛又爽又黄的视频四季| 欧美性色视频在线| 亚洲 国产 欧美 日韩| 国内精品小视频| 国产精品色呦| 免费人成在线观看视频播放| 国产91在线看| 久久久久久激情| 日韩一区二区精品| 国产激情小视频在线| 91美女福利视频高清| 久久久久久免费视频| 超碰在线资源站| 亚洲女人****多毛耸耸8| 国产女人18毛片水真多| 精品国产拍在线观看| 日韩黄色碟片| 三上悠亚免费在线观看| 国产成人午夜精品5599| 激情五月婷婷小说| 精品少妇一区二区三区在线播放 | 国产十八熟妇av成人一区| 亚洲成av人影院在线观看网| 亚洲欧洲成人在线| 热99精品只有里视频精品| 国产探花一区在线观看| 不卡的av中文字幕| 亚洲精品亚洲人成人网在线播放| www.激情五月| 欧美性在线视频| 精品国产一区二区三区久久久樱花| 鲁一鲁一鲁一鲁一av| 椎名由奈av一区二区三区| www.久久精品.com| 91精品国产免费久久久久久| 精品一区电影| 欧美体内she精高潮| 午夜成人免费视频| 日本国产在线| 成人激情视频网| 亚洲国内自拍| 无码一区二区三区在线| 欧美日韩视频在线一区二区| 丝袜国产在线| 欧美激情一区二区三区在线视频| 蜜臀av性久久久久av蜜臀妖精| 欧美成人aaa片一区国产精品| 日韩av在线直播| 久久av影院| 欧美精品久久久久久久自慰| 国产喷白浆一区二区三区| 国产情侣av在线| 欧美一区二三区| 亚洲精品a级片| 亚洲一区二区乱码| 欧美日韩国产小视频在线观看| 日韩另类在线| 欧美亚洲丝袜| 国产盗摄精品一区二区三区在线| 女人十八岁毛片| 久久久国产精品一区| 日韩三级视频| 深爱五月综合网| 欧美影片第一页| 金瓶狂野欧美性猛交xxxx| 午夜精品一区二区在线观看| 国产.精品.日韩.另类.中文.在线.播放| www.国产毛片| 欧美精品18videosex性欧美| 91亚洲一区|