首頁>技術>

併發程式設計算是Java的一個難點,經常做業務相關的程式設計師基本上用不到juc的包,但是這些知識點十分重要,所以不管在哪裡,時刻保持學習真的很重要。

(一)AQS概述

Java併發程式設計的核心在於java.concurrent.util包,juc中大多數同步器的實現都圍繞了一個公共的行為,比如等待佇列、條件佇列、獨佔獲取、共享獲取等,這個行為的抽象就是基於AbstractQueuedSynchronized(AQS)。AQS定義了多執行緒訪問共享資源的同步器框架。

簡單來講,AQS就好比一個行為準則,而併發包中的大多數同步器在這個準則下實現。

AQS具備以下的幾個特性:阻塞等待佇列、共享/獨佔、公平/非公平、可重入、允許中斷。

如果你點開JUC發源碼,會發現大量同步器的實現,比如:Lock、Latch、Barrier等都基於AQS實現。

(二)幾個重要的知識點

在AQS中,我們需要記住幾個重要的知識點:

1、AQS的實現通常是定義內部類Sync繼承AQS,將同步器的所有呼叫都對映到Sync對應的方法上。

2、AQS內部有個屬性叫state,表示資源的可用狀態。state有三種訪問方式getState()、setState()、compareAndSetState()

3、AQS定義了兩種資源的共享方式:獨佔(Exclusive)如ReentrantLock、共享(Share)如Semaphore或CountDownLatch

4、AQS中定義了同步等待佇列,用於存放等待執行緒的一個佇列。

這幾個知識點會在後面的內容中使用到。

(三)ReentrantLock

我們透過ReentrantLock這個示例來更深入的瞭解AQS。我會透過上面四個知識點去講解ReentrantLock中AQS的使用。

1、首先進入ReentrantLock的原始碼內部,直接就能看到ReentrantLock中定義的內部類Sync

Sync繼承了AQS,按AQS去指定同步規則。

2、既然繼承了AQS,ReentrantLock內部也相當於有了state,這個state用來記錄上鎖的次數,ReentrantLock是個可重入鎖,如果多次上鎖,state會記錄上鎖的次數,需要釋放同樣次數的鎖才算把鎖釋放完。

3、ReentrantLock的資源是獨佔的,AbstractQueuedSynchronized繼承了一個叫AbstractOwnableSynchronizer的抽象類:

在這個類中,有個變數叫exclusiveOwnerThread,這個變數記錄著當前是哪個執行緒獨佔了鎖。

4、同步等待佇列:由於ReentrantLock是個獨佔的鎖,當有一個執行緒在使用這個鎖的時候,其他執行緒就要到佇列中去等待,這個佇列是一種基於雙向連結串列的佇列(類CLH佇列),節點中存放執行緒資訊。

(四)可重入鎖

在介紹AQS時,我們講到了AQS中有個狀態值state,這個值用來判斷當前資源的可用狀態。可重入鎖的意思就是對一個物件可以實現多次加鎖,state就用來記錄加鎖的次數。下面寫一段程式碼:

public class ReentrantLockTest {    //定義全域性的鎖物件    private static final Lock lock=new ReentrantLock(true);    public static int count=0;    public static void main(String[] args) {        new Thread(new Runnable() {            @Override            public void run() {                testlock();            }        },"執行緒A").start();        new Thread(new Runnable() {            @Override            public void run() {                testlock();            }        },"執行緒B").start();    }    private static void testlock() {        lock.lock();        count++;        System.out.println(Thread.currentThread().getName()+"第一次加鎖"+count);        lock.lock();        count++;        System.out.println(Thread.currentThread().getName()+"第二次加鎖"+count);        count--;        lock.unlock();        System.out.println(Thread.currentThread().getName()+"第一次解鎖"+count);        count--;        lock.unlock();        System.out.println(Thread.currentThread().getName()+"第二次解鎖"+count);    }}

生成兩個執行緒,讓他們去執行testlock方法,然後在testlock方法的開始和結束加鎖,保證同時只有一個執行緒可以執行裡面的方法。最後的結果是執行緒有序執行:

在程式碼中,我們進行了兩次lock,這就是可重入鎖。我們透過斷點除錯,來分析第二次加鎖後lock中的值,下面給出了說明。

(五)公平鎖與非公平鎖

我們在用構造方法建立ReentrantLock的時候,可以傳入一個boolean型別的引數,true或false

private static final Lock lock=new ReentrantLock(true);

這裡的true和false代表了建立的ReentrantLock物件是公平鎖還是非公平鎖

透過上文的學習,我們知道當有執行緒在使用鎖的時候,其他執行緒是處於等待佇列中的,而一旦鎖被釋放後,他會去喚醒等待佇列中的第一個鎖

如果是公平鎖,當有新的執行緒來的時候,他會先去看看等待佇列中有沒有等待的執行緒,如果有,則乖乖跑到最後去排隊

如果是非公平鎖,當有新的執行緒來的時候,直接看state的狀態,如果發現是0,不管等待佇列有沒有等待的執行緒,直接去和被喚醒的鎖競爭,如果競爭失敗了,則乖乖跑到佇列最後去排隊,否則就直接佔有鎖。

今天就分享到這裡了

原文連結:https://my.oschina.net/u/4873431/blog/4839523

14
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • C語言的2個小例子,搞明白陣列不再迷茫