synchronized 和 reentrantlock 區別是什么_JUC源碼系列之ReentrantLock源碼解析

f78ad27c800b0a907dc87ae2aa74b3dd.png

目錄

  • ReentrantLock 簡介
  • ReentrantLock 使用示例
  • ReentrantLock 與 synchronized 的區別
  • ReentrantLock 實現原理
  • ReentrantLock 源碼解析
a4ceffdeca1a5b9d7d79f4a28f03a63c.png

ReentrantLock 簡介

ReentrantLock 是 JDK 提供的一個可重入的獨占鎖,

  • 獨占鎖:同一時間只有一個線程可以持有鎖
  • 可重入:持有鎖的線程可重復加鎖,意味著第一次成功加鎖時,需要記錄鎖的持有者為當前線程,后續再次加鎖時,可以識別當前線程。

ReentrantLock 提供了公平鎖以及非公平鎖兩種模式,要解釋這兩種模式不異同,得先了解一下 ReentrantLock 的加鎖流程,ReentrantLock 基于 AQS 同步器實現加解鎖,基本的實現流程為:

線程 A、B、C 同時執行加鎖,加鎖是通過CAS操作完成,CAS 是原子操作,可以保證同一時間只有一個線程加鎖成功,假設線程 A 加鎖成功,則線程 B、C 進入 AQS 等待隊列并被掛起,假設 B 在前,C 在后,當線程 A 釋放鎖時,會喚醒排在等待隊列隊首的線程 B,該線程會嘗試通過 CAS 進行獲取鎖。如果線程 B 嘗試加鎖的同時,有線程 D 也同時進行加鎖,如果線程 D 與 線程 B 競爭加鎖,則為非公平鎖,線程 D 加入等待隊列排在線程 C 之后,則為公平鎖。

  • 非公平鎖:加鎖時會與等待隊列中的頭節點進行競爭。
  • 公平鎖:加鎖時首先判斷等待隊列中是否有線程在排隊,如果沒有則參與競爭鎖,如果有則排隊等待。

所謂公平就是,大家一起到,就競爭上崗,如果已經有人在排隊了,那就先來后到。

使用示例

使用偽代碼表示

class X {    private final ReentrantLock lock = new ReentrantLock();    public void m() {        lock.lock();  // block until condition holds        try {            // ... method body        } finally {            lock.unlock();        }    }}

默認實現的是非公平鎖,如果要使用公平鎖,只需要創建 ReentrantLock 對象時傳遞入參 true 即可,使用方法與非公平鎖一樣。

private final ReentrantLock lock = new ReentrantLock(true);

condition 使用示例:

class X {    private final ReentrantLock lock = new ReentrantLock();    private final Condition condition = lock.newCondition();    public void poll() {        lock.lock();  // block until condition holds        try {            while(條件判斷表達式) {                condition.wait();            }        } finally {            lock.unlock();        }    }        public void push() {        condition.signal();    }}

ReentrantLock 與 synchronized 的區別

ReentrantLock 提供了 synchronized 類似的功能和內存語義。

相同點

  • ReentrantLock 與 synchronized 都是獨占鎖,可以讓程序正確同步。
  • ReentrantLock 與 synchronized 都可重入鎖,可以在循環中使用 synchronized 進行加鎖并不用擔心解鎖問題,但 ReentrantLock 則必須要進行與加鎖相同次數的解鎖操作,不然可能導致沒有真正解鎖成功。

不同點

  • synchronized 是JDK提供的語法,加鎖解鎖的過程是隱式的,用戶不用手動操作,操作簡單,但不夠靈活。
  • ReentrantLock 需要手動加鎖解鎖,且解鎖次數必須與加鎖次數一樣,才能保證正確釋放鎖,操作較為復雜,但是因為是手動操作,所以可以應付復雜的并發場景。
  • ReentrantLock 可以實現公平鎖
  • ReentrantLock 可以響應中斷,使用 lockInterruptibly 方法進行加鎖,可以在加鎖過程中響應中斷,synchronized 不能響應中斷
  • ReentrantLock 可以實現快速失敗,使用 tryLock 方法進行加鎖,如果不能加鎖成功,會立即返回 false,而 synchronized 是阻塞式。
  • ReentrantLock 可以結合 Condition 實現條件機制。

可以看到,ReentrantLock 與 synchronized 都是實現線程同步加鎖,但 ReentrantLock 比起 synchronized 要靈活很多。

a43a402d693fa2074913cdf259d05301.png

實現原理

ReentrantLock 使用組合的方式,通過繼承 AQS 同步器實現線程同步。通過控制 AQS 同步器的同步狀態 state 達到加鎖解鎖的效果,該狀態默認為 0,代表鎖未被占用,加鎖則是通過 cas 操作將其設置為 1,cas 是原子性操作,可以保證同一時間只有一個線程可以加鎖成功,同一個線程可以重復加鎖,每次加鎖同步狀態自增 1,釋放鎖的過程就是將同步狀態自減,減到 0 時才算完全釋放,這也解釋了為什么釋放鎖的次數必須與加鎖次數一樣的問題,因為只有次數一樣才能將同步狀態減至 0,這樣其它線程才能進行加鎖。

源碼分析

Lock 接口

ReentrantLock 實現了 Lock 接口,這是 JDK 提供的所有 JVM 鎖的基類。

public interface Lock {    // 阻塞式加鎖    void lock();    // 阻塞式加鎖,但可以響應中斷,加鎖過程中線程中斷,拋出 InterruptedException 異常    void lockInterruptibly() throws InterruptedException;    // 快速失敗加鎖,只嘗試一次,    boolean tryLock();    // 阻塞式加鎖,可以響應中斷并且實現超時失敗    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;    // 釋放鎖    void unlock();    // 實現條件    Condition newCondition();}

通過代碼可以看到,ReentrantLock 的內部實現都是通過 Sync 這個類實現,可以認為遵守組合設計原則,Sync 是 ReentrantLock 的內部類。這里的方法調用,并沒有區分是公平鎖還是非公平鎖,而是無差別地調用,所以區別一定在 Sync 這個類的實現中。

public class ReentrantLock implements Lock, java.io.Serializable {    private final Sync sync;    public void lock() {        sync.lock();    }    public void lockInterruptibly() throws InterruptedException {        sync.acquireInterruptibly(1);    }    public boolean tryLock() {        return sync.nonfairTryAcquire(1);    }    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {        return sync.tryAcquireNanos(1, unit.toNanos(timeout));    }    public void unlock() {        sync.release(1);    }    public Condition newCondition() {        return sync.newCondition();    }}

Sync 類繼承了 AQS 同步器,通過同步器實現線程同步,因為是獨占鎖,所以最重要的就是實現 tryAcquire 與 tryRelease 兩個方法,Sync 類是一個 abstract 類,它擁有兩個實現類 FairSync 與 NonfairSync,通過名字應該就可分辨他們就是公平鎖與非公平鎖。

abstract static class Sync extends AbstractQueuedSynchronizer {    abstract void lock();    // 非公平加鎖    final boolean nonfairTryAcquire(int acquires) {        final Thread current = Thread.currentThread();        int c = getState();        if (c == 0) { // 如果沒有線程執行鎖            if (compareAndSetState(0, acquires)) { // 通過 CAS 嘗試加鎖                setExclusiveOwnerThread(current); // 加鎖成功,設置鎖的擁有者為當前線程                return true;            }        } else if (current == getExclusiveOwnerThread()) { // 如果鎖已經被當前線程占有,說明是重復加鎖            int nextc = c + acquires; // 將同步狀態進行自增,acquires 的傳值為 1            if (nextc < 0) // overflow                throw new Error("Maximum lock count exceeded");            setState(nextc);            return true;        }        return false;    }    // 釋放鎖    protected final boolean tryRelease(int releases) {        int c = getState() - releases; // 將同步狀態進行自減,acquires 的傳值為 1        if (Thread.currentThread() != getExclusiveOwnerThread())            throw new IllegalMonitorStateException();        boolean free = false;        if (c == 0) { // 當同步狀態減成 0 時,代表完全釋放鎖,將鎖的擁有者置空            free = true;            setExclusiveOwnerThread(null);        }        setState(c);        return free;    }    // ...省略其它...}

所以公平鎖與非公平鎖的玄機就在 ReentrantLock 的構造方法中,默認的無參構造方法創建非公平鎖,如果傳參 true,則創建公平鎖。而這兩個鎖都是 Sync 的子類,使用了不同的實現策略,可以認為使用了策略模式。

public class ReentrantLock implements Lock, java.io.Serializable {    // 默認創建非公平鎖    public ReentrantLock() {        sync = new NonfairSync();    }    // 如果為 true,則創建公平鎖    public ReentrantLock(boolean fair) {        sync = fair ? new FairSync() : new NonfairSync();    }}

接下來分別看一下 FairSync 與 NonfairSync 是如何實現公平鎖與非公平鎖的,首先分析非公平鎖

static final class NonfairSync extends Sync {    // 阻塞式加鎖    final void lock() {        // 首先嘗試競爭加鎖,如果成功則設置當前線程為鎖的擁有者        if (compareAndSetState(0, 1))             setExclusiveOwnerThread(Thread.currentThread());        else            acquire(1); // 使用 AQS 排隊    }    // 嘗試加鎖    protected final boolean tryAcquire(int acquires) {        return nonfairTryAcquire(acquires);    }}

阻塞式加鎖調用 ReentrantLock 的 lock() 方法,該方法調用 sync.lock() 執行加鎖,非公平鎖也就是調用 NonfairSync 類的 lock 方法,該方法首先嘗試競爭加鎖,此時有三種情況:

  • 此時鎖沒有人持有,競爭成功,直接設置當前線程為鎖的擁有者并返回
  • 此時鎖沒有人持有,競爭失敗,走 AQS 加鎖流程
  • 此時鎖被其它線程擁有,走 AQS 加鎖流程
  • 此時鎖被自己擁有,競爭失敗,走 AQS 加鎖流程

AQS 加鎖流程就是調用 tryAcquire 方法嘗試加鎖,如果成功則返回加鎖成功,如果失敗則進入等待隊列并掛起,等待鎖的持有者釋放鎖時喚醒等待隊列中的線程,并再次嘗試加鎖,如此反復,直到加鎖成功。

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

AQS 加鎖流程在 AQS 中已經提供了完整實現模板,不需要去了解底層就可以使用,需要做的就是自行實現 tryAcquire 方法,NonfairSync 的 tryAcquire 方法這里再貼一次實現代碼。

final boolean nonfairTryAcquire(int acquires) {    final Thread current = Thread.currentThread();    int c = getState();    if (c == 0) { // 如果沒有線程執行鎖        if (compareAndSetState(0, acquires)) { // 通過 CAS 嘗試加鎖            setExclusiveOwnerThread(current); // 加鎖成功,設置鎖的擁有者為當前線程            return true;        }    } else if (current == getExclusiveOwnerThread()) { // 如果鎖已經被當前線程占有,說明是重復加鎖        int nextc = c + acquires; // 將同步狀態進行自增,acquires 的傳值為 1        if (nextc < 0) // overflow            throw new Error("Maximum lock count exceeded");        setState(nextc);        return true;    }    return false;}

接下來看一下 FairSync 的實現

static final class FairSync extends Sync {    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)) {                setExclusiveOwnerThread(current);                return true;            }        } else if (current == getExclusiveOwnerThread()) { // 如果鎖已經被當前線程持有,與非公平鎖同樣處理            int nextc = c + acquires; // 將同步狀態進行自增,acquires 的傳值為 1            if (nextc < 0)                throw new Error("Maximum lock count exceeded");            setState(nextc);            return true;        }        return false;    }}

釋放鎖的過程,公平鎖與非公平鎖是一樣的,前面的代碼中已經解釋過了,這里就不再多說了。

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

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

相關文章

gulp 和npm_為什么我離開Gulp和Grunt去看npm腳本

gulp 和npmI know what you’re thinking. WAT?! Didn’t Gulp just kill Grunt? Why can’t we just be content for a few minutes here in JavaScript land? I hear ya, but…我知道你在想什么 WAT &#xff1f;&#xff01; 古爾普不是殺死了咕unt嗎&#xff1f; 為什么…

mysql8.0遞歸_mysql8.0版本遞歸查詢

1.先在mysql數據庫添加數據DROP TABLE IF EXISTS dept;CREATE TABLE dept (id int(11) NOT NULL,pid int(11) DEFAULT NULL,name varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,date datetime(0) DEFAULT NULL,PRIMARY KEY (id) USING BTREE) ENGINE…

js 輪播插件

flexslider pc插件 個人用過flickerplate 移動端插件 個人用過個人覺得比較好的移動端插件swiper http://www.swiper.com.cn/ 用過個人覺得比較好的pc端插件待定

計算機中的字符編碼

字符編碼 什么是計算機編碼 計算機只能處理二進制的數據&#xff0c;其它的數據都要進行轉換&#xff0c;但轉換必須要有一套字符編碼(是字符與二進制的一個對應關系)。常用的字符&#xff1a;a-z、0-9、其它的符號等&#xff0c;計算機也不能直接處理。 &#xff08;字符編碼類…

致力微商_致力于自己。 致力于公益組織。

致力微商by freeCodeCamp通過freeCodeCamp 致力于自己。 致力于公益組織。 (Commit to Yourself. Commit to a Nonprofit.) In case you missed it, our October Summit was jam-packed with several big announcements about our open source community.如果您錯過了它&#…

應急照明市電檢測_應急照明如何供電? 如何接線? 圖文分析!

對于大部分剛接觸建筑電氣設計的工作者來說&#xff0c;應急照明的強啟原理一直都是很頭疼的問題。由于不知道應急照明的強啟原理&#xff0c;所以&#xff0c;應急燈具應該用多少根線&#xff0c;其實也就無從談起。下面以文字和圖片結合的方式來說明應急燈怎么接線的&#xf…

win10網速慢

升級到win10之后發現網速特別慢&#xff0c;搜了下&#xff0c;網上的解決辦法果然好使&#xff0c;按照如下操作即可。 返回桌面&#xff0c;按WINR鍵組合&#xff0c;運行gpedit.msc 打開組策略 依次展開管理模板-》網絡-》QoS數據計劃程序-》限制可保留寬帶&#xff0c;雙擊…

ubuntu安裝nodejs

下載nodejs https://nodejs.org/dist/v4.6.0/node-v4.6.0-linux-x64.tar.gz 解壓 tar -zxvf node-v4.6.0-linux-x64.tar.gz 移動到/opt/下 mv node-v4.6.0-linux-x64 /opt/ 創建鏈接 ln -s /opt/node-v4.6.0-linux-x64/bin/node /usr/local/bin/node 轉載于:https://www.cnblog…

android實用代碼

Android實用代碼七段&#xff08;一&#xff09; 前言 這里積累了一些不常見確又很實用的代碼&#xff0c;每收集7條更新一次&#xff0c;希望能對大家有用。 聲明 歡迎轉載&#xff0c;但請保留文章原始出處:)     博客園&#xff1a;http://www.cnblogs.com 農民伯伯&…

mysql 全文本檢索的列_排序數據列以檢索MySQL中的最大文本值

為此&#xff0c;您可以將ORDER BY與一些聚合函數right()一起使用。讓我們首先創建一個表-create table DemoTable1487-> (-> StudentCode text-> );使用插入命令在表中插入一些記錄-insert into DemoTable1487 values(560);insert into DemoTable1487 values(789);in…

關于長壽_FreeCodeCamp可以幫助您更長壽

關于長壽by Christopher Phillips克里斯托弗菲利普斯(Christopher Phillips) 免費代碼營可能幫助您長壽 (Free Code Camp Might Help You Live Longer) Since I started my web development journey with Free Code Camp, I’ve felt more awake, more alert, and able to pro…

python世界你好的輸出便攜電源適配器_65W PD輸出,Thinkplus USB-C便攜電源適配器(PA65)開箱評測...

包裝盒底蓋面為紅色&#xff0c;標注了產品的相關參數&#xff1a;型號&#xff1a;PA65&#xff1b;輸入&#xff1a;100V-240V~50/60Hz 1.5A&#xff1b;輸出&#xff1a;5V/3A、9V/3A、12V/3A、15V/3A、20V/3.25A&#xff1b;制造商&#xff1a;南京博蘭得電子科技有限公司&…

歸并排序與逆序對

在刷題的過程中碰到了關于無序序列的逆序對統計的問題。 直接暴力會超時&#xff0c;然后搜索了一下算法&#xff0c;發現可以通過歸并排序的思想來做到這個統計的過程。看代碼的時候&#xff0c;不知道自己的理解力不夠還是不熟悉別人的代碼&#xff0c;反正是看不懂。無奈之下…

c#獲取pdf文件頁數

引用命名空間&#xff1a;using iTextSharp.text.pdf; string filePath Server.MapPath("/upload/123.pdf"); //文件的物理路徑PdfReader reader new PdfReader(filePath);int iPageNum reader.NumberOfPages; //文件頁數reader.Close();Response.Write("文件…

VS2015 Cordova Ionic移動開發(五)

一、創建側邊菜單和導航項目 1.使用VS創建一個Ionic空項目&#xff0c;同時創建一個Ionic SideMenu和Ionic Tabs項目。將SideMenu和Tabs項目里的templates和js文件合并到空項目里&#xff0c;修改js對應的代碼即可。完整項目工程如下&#xff1a; 2.App.js代碼修改如下&#xf…

最佳適應算法和最壞適應算法_算法:好,壞和丑陋

最佳適應算法和最壞適應算法by Evaristo Caraballo通過Evaristo Caraballo 算法&#xff1a;好&#xff0c;壞和丑陋 (Algorithms: The Good, The Bad and The Ugly) Who has been in Free Code Camp without having the experience of spending hours trying to solve Algori…

mysql條件觸發器實例_mysql觸發器實例一則

例子&#xff0c;實例學習mysql觸發器的用法。一&#xff0c;準備二張測試表&#xff1a;1&#xff0c;測試表1復制代碼 代碼示例:DROP TABLE IF EXISTS test;CREATE TABLE test (id bigint(11) unsigned NOT NULL AUTO_INCREMENT,name varchar(100) NOT NUL…

阿里大數據神預測 勝率僅5.9%中國卻1:0勝韓國

寫在最前面&#xff1a;這是早晨偶然看到的一篇文章&#xff0c;是對昨天中國卻1&#xff1a;0勝韓國的評論。有朋友感慨&#xff1a;努力不放棄的時候&#xff0c;全世界都會幫你。這篇內容很全面的串起阿里巴巴在大數據預測方面的動作&#xff0c;角度很別致&#xff0c;分享…

Python中類、對象與self詳解

先介紹一下python中的類與對象/實例。然后詳細說明self。說明&#xff1a;對象等同實例&#xff0c;本文稱呼不一致時請自行統一 【一】類與對象/實例 1、類 &#xff08;1&#xff09;類由名稱、屬性、方法三部分組成 &#xff08;2&#xff09;類是抽象模板&#xff0c;比如學…

面試題28 字符串排列

題目描述 輸入一個字符串,按字典序打印出該字符串中字符的所有排列。例如輸入字符串abc,則打印出由字符a,b,c所能排列出來的所有字符串abc,acb,bac,bca,cab和cba。 結果請按字母順序輸出。 輸入描述: 輸入一個字符串,長度不超過9(可能有字符重復),字符只包括大小寫字母。 1 cla…