synchronized與ReentrantLock(Lock的默認(rèn)實(shí)現(xiàn))類

synchronized詳解

  • 公平鎖是指當(dāng)鎖可用時(shí),在鎖上等待時(shí)間最長(zhǎng)的線程將獲得鎖的使用權(quán)。
  • 非公平鎖則隨機(jī)分配這種使用權(quán)种呐。
synchronized的三種使用方式
  • 修飾實(shí)例方法(所謂實(shí)例對(duì)象鎖就是用synchronized修飾實(shí)例對(duì)象的實(shí)例方法,注意是實(shí)例方法彰亥,不是靜態(tài)方法)
  • 修飾靜態(tài)方法(當(dāng)synchronized作用于靜態(tài)方法時(shí),鎖的對(duì)象就是當(dāng)前類的Class對(duì)象)
  • 修飾代碼塊(手動(dòng)指定鎖定對(duì)象,也可是是this,也可以是自定義的鎖)

// 修飾實(shí)例方法
public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence = new SynchronizedObjectLock();

    @Override
    public void run() {
        method();
    }

    public synchronized void method() {
        System.out.println("我是線程" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "結(jié)束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instence);
        Thread t2 = new Thread(instence);
        t1.start();
        t2.start();
    }
}輸出結(jié)果:
  我是線程Thread-0  Thread-0結(jié)束  我是線程Thread-1  Thread-1結(jié)束
// 修飾靜態(tài)方法
public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
    static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();

    @Override
    public void run() {
        method();
    }

    // synchronized用在普通方法上杏头,默認(rèn)的鎖就是this,當(dāng)前實(shí)例
    public synchronized void method() {
        System.out.println("我是線程" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "結(jié)束");
    }

    public static void main(String[] args) {
        // t1和t2對(duì)應(yīng)的this是兩個(gè)不同的實(shí)例沸呐,所以代碼不會(huì)串行
        Thread t1 = new Thread(instence1);
        Thread t2 = new Thread(instence2);
        t1.start();
        t2.start();
    }
}輸出結(jié)果:
  我是線程Thread-0  我是線程Thread-1  Thread-1結(jié)束  Thread-0結(jié)束
//修飾代碼塊
public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence = new SynchronizedObjectLock();

    @Override
    public void run() {
        // 同步代碼塊形式——鎖為this,兩個(gè)線程使用的鎖是一樣的,線程1必須要等到線程0釋放了該鎖后醇王,才能執(zhí)行
        synchronized (this) {
            System.out.println("我是線程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "結(jié)束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instence);
        Thread t2 = new Thread(instence);
        t1.start();
        t2.start();
    }
}輸出結(jié)果:  我是線程Thread-0  Thread-0結(jié)束  我是線程Thread-1  Thread-1結(jié)束
synchronized的性質(zhì)

1.可重入性
概念:指同一個(gè)線程外層函數(shù)獲取到鎖之后,內(nèi)層函數(shù)可以直接使用該鎖

好處:避免死鎖崭添,提升封裝性(如果不可重入寓娩,假設(shè)method1拿到鎖之后,在method1中又調(diào)用了method2,如果method2沒辦法使用method1拿到的鎖,那method2將一直等待棘伴,但是method1由于未執(zhí)行完畢寞埠,又無法釋放鎖,就導(dǎo)致了死鎖排嫌,可重入正好避免這這種情況)但是synchronized的使用也有可能會(huì)出現(xiàn)死鎖,這里的避免死鎖的更底層的概念缰犁。
2.不可中斷性

概念:如果這個(gè)鎖被B線程獲取淳地,如果A線程想要獲取這把鎖,只能選擇等待或者阻塞帅容,直到B線程釋放這把鎖颇象,如果B線程一直不釋放這把鎖,那么A線程將一直等待并徘。

相比之下遣钳,未來的Lock類,可以擁有中斷的能力(如果一個(gè)線程等待鎖的時(shí)間太長(zhǎng)了麦乞,有權(quán)利中斷當(dāng)前已經(jīng)獲取鎖的線程的執(zhí)行蕴茴,也可以退出等待)

synchronized的原理
public class SynchronizedDemo2 {
    Object object = new Object();

    public void method1() {
        synchronized (object) {

        }
    }
}
使用javac命令進(jìn)行編譯生成.class文件

>javac SynchronizedDemo2.java

使用javap命令反編譯查看.class文件的信息

>javap -verbose SynchronizedDemo2.class
得到如下的信息:
關(guān)注紅色方框里的monitorenter和monitorexit即可。
image.png

Monitorenter和Monitorexit指令姐直,會(huì)讓對(duì)象在執(zhí)行倦淀,使其鎖計(jì)數(shù)器加1或者減1。每一個(gè)對(duì)象在同一時(shí)間只與一個(gè)monitor(鎖)相關(guān)聯(lián)声畏,而一個(gè)monitor在同一時(shí)間只能被一個(gè)線程獲得撞叽,一個(gè)對(duì)象在嘗試獲得與這個(gè)對(duì)象相關(guān)聯(lián)的Monitor鎖的所有權(quán)的時(shí)候,monitorenter指令會(huì)發(fā)生如下3中情況之一:

1)monitor計(jì)數(shù)器為0插龄,意味著目前還沒有被獲得愿棋,那這個(gè)線程就會(huì)立刻獲得然后把鎖計(jì)數(shù)器+1,一旦+1均牢,別的線程再想獲取糠雨,就需要等待

2)如果這個(gè)monitor已經(jīng)拿到了這個(gè)鎖的所有權(quán),又重入了這把鎖徘跪,那鎖計(jì)數(shù)器就會(huì)累加见秤,變成2,并且隨著重入的次數(shù)真椿,會(huì)一直累加

3)這把鎖已經(jīng)被別的線程獲取了鹃答,等待鎖釋放

monitorexit指令:釋放對(duì)于monitor的所有權(quán),釋放過程很簡(jiǎn)單突硝,就是講monitor的計(jì)數(shù)器減1测摔,如果減完以后,計(jì)數(shù)器不是0,則代表剛才是重入進(jìn)來的锋八,當(dāng)前線程還繼續(xù)持有這把鎖的所有權(quán)浙于,如果計(jì)數(shù)器變成0,則代表當(dāng)前線程不再擁有該monitor的所有權(quán)挟纱,即釋放鎖羞酗。

2.可重入原理:加鎖次數(shù)計(jì)數(shù)器

jvm會(huì)負(fù)責(zé)跟蹤對(duì)象被加鎖的次數(shù)

線程第一次獲得所,計(jì)數(shù)器+1紊服,當(dāng)鎖重入的時(shí)候檀轨,計(jì)數(shù)器會(huì)遞增

當(dāng)任務(wù)離開的時(shí)候(一個(gè)同步代碼塊的代碼執(zhí)行結(jié)束),計(jì)數(shù)器會(huì)減1欺嗤,當(dāng)減為0的時(shí)候参萄,鎖被完全釋放。

synchronized的缺陷
  • 效率低:鎖的釋放情況少煎饼,只有代碼執(zhí)行完畢或者異常結(jié)束才會(huì)釋放鎖讹挎;試圖獲取鎖的時(shí)候不能設(shè)定超時(shí),不能中斷一個(gè)正在使用鎖的線程吆玖,相對(duì)而言筒溃,Lock可以中斷和設(shè)置超時(shí)

  • 不夠靈活:加鎖和釋放的時(shí)機(jī)單一,每個(gè)鎖僅有一個(gè)單一的條件(某個(gè)對(duì)象)沾乘,相對(duì)而言铡羡,讀寫鎖更加靈活

  • 無法知道是否成功獲得鎖,相對(duì)而言意鲸,Lock可以拿到狀態(tài)烦周,如果成功獲取鎖,....怎顾,如果獲取失敗,.....

3.可見性原理:

  • 在加鎖前會(huì)將工作內(nèi)存的值全部重新加載一遍读慎,保證最新;釋放鎖前將工作內(nèi)存的值全部更新到主存槐雾;由于在帶鎖期間夭委,沒有其他線程能訪問本線程正在使用的共享變量,這樣就保證了可見性
  • 由于Synchronized修飾的代碼塊都是原子性執(zhí)行的募强,即一旦開始做株灸,就會(huì)一直執(zhí)行完畢,期間有其他線程不可以訪問本線程所使用的共享變量擎值,這樣慌烧,即便指令重排了也不會(huì)出現(xiàn)問題。

4.synchronized非公平鎖原理:synchronized底層使用操作系統(tǒng)mutext鎖實(shí)現(xiàn)的鸠儿,而mutext鎖并不保證公平性屹蚊,這樣做的目的就是為了提高執(zhí)行性能厕氨,當(dāng)然缺點(diǎn)是有可能會(huì)產(chǎn)生線程饑餓。

synchronized和@Transactional一起使用時(shí)出現(xiàn)了synchronized失效的原因

1.@Transactional 注解是Spring 提供來進(jìn)行控制事務(wù)的注解汹粤,當(dāng)注解標(biāo)明在方法上時(shí)命斧, 則會(huì)對(duì) 該方法進(jìn)行做AOP 增強(qiáng)(動(dòng)態(tài)代理),然后在方法執(zhí)行前嘱兼,開啟事務(wù)国葬,執(zhí)行后提交事務(wù)。
2.是因?yàn)镾ynchronized鎖定的是當(dāng)前調(diào)用方法對(duì)象,而Spring AOP 處理事務(wù)會(huì)進(jìn)行生成一個(gè)代理對(duì)象芹壕,并在代理對(duì)象執(zhí)行方法前的事務(wù)開啟汇四,方法執(zhí)行完的事務(wù)提交,所以說哪雕,事務(wù)的開啟和提交并不是在 Synchronized 鎖定的范圍內(nèi)船殉。出現(xiàn)同步鎖失效的原因是:當(dāng)A(線程) 執(zhí)行完insert()方法鲫趁,會(huì)進(jìn)行釋放同步鎖斯嚎,去做提交事務(wù),但在A(線程)還沒有提交完事務(wù)之前挨厚,B(線程)進(jìn)行執(zhí)行insert() 方法堡僻,執(zhí)行完畢之后和A(線程)一起提交事務(wù), 這時(shí)候就會(huì)出現(xiàn)線程安全問題,就會(huì)插入重復(fù)數(shù)據(jù)疫剃。

cglib代理synchronized的方法钉疫,同步關(guān)鍵字是否還在?

在動(dòng)態(tài)生成的子類中不存在synchronized巢价,但synchronized會(huì)有效果牲阁。因?yàn)閯?dòng)態(tài)代理回去調(diào)用父類的方法,父類的方法是被鎖住的壤躲。

Synchronized本質(zhì)上是通過什么保證線程安全的城菊?分三個(gè)方面回答:加鎖和釋放鎖的原理,可重入原理碉克,保證可見性原理凌唬。
Synchronized由什么樣的缺陷?Java Lock是怎么彌補(bǔ)這些缺陷的漏麦。
  • 1.當(dāng)線程嘗試獲取鎖的時(shí)候客税,如果獲取不到鎖會(huì)一直阻塞,這個(gè)阻塞的過程撕贞,用戶無法控制
  • 2.如果獲取鎖的線程進(jìn)入休眠或者阻塞更耻,除非當(dāng)前線程異常,否則其他線程嘗試獲取鎖必須一直等待
Synchronized和Lock的對(duì)比捏膨,和選擇酥夭?
Synchronized在使用時(shí)有何注意事項(xiàng)?
Synchronized修飾的方法在拋出異常時(shí),會(huì)釋放鎖嗎?
  • 線程在執(zhí)行同步方法時(shí)拋出異常熬北,會(huì)自動(dòng)釋放鎖疙描,以便其他線程可以拿到鎖繼續(xù)執(zhí)行。
多個(gè)線程等待同一個(gè)synchronized鎖的時(shí)候讶隐,JVM如何選擇下一個(gè)獲取鎖的線程起胰?
  • 這個(gè)問題就涉及到內(nèi)部鎖的調(diào)度機(jī)制,線程獲取 synchronized 對(duì)應(yīng)的鎖巫延,也是有具體的調(diào)度算法的效五,這個(gè)和具體的虛擬機(jī)版本和實(shí)現(xiàn)都有關(guān)系,所以下一個(gè)獲取鎖的線程是事先沒辦法預(yù)測(cè)的炉峰。
Synchronized使得同時(shí)只有一個(gè)線程可以執(zhí)行畏妖,性能比較差,有什么提升的方法疼阔?
  • 優(yōu)化 synchronized 的使用范圍戒劫,讓臨界區(qū)的代碼在符合要求的情況下盡可能的小。
  • 使用其他類型的 lock(鎖)婆廊,synchronized 使用的鎖經(jīng)過 jdk 版本的升級(jí)迅细,性能已經(jīng)大幅提升了,但相對(duì)于更加輕量級(jí)的鎖(如讀寫鎖)還是偏重一點(diǎn)淘邻,所以可以選擇更合適的鎖茵典。
我想更加靈活地控制鎖的釋放和獲取(現(xiàn)在釋放鎖和獲取鎖的時(shí)機(jī)都被規(guī)定死了),怎么辦宾舅?
  • 可以根據(jù)需要實(shí)現(xiàn)一個(gè) Lock 接口统阿,這樣鎖的獲取和釋放就能完全被我們控制了。
什么是鎖的升級(jí)和降級(jí)筹我?什么是JVM里的偏斜鎖扶平、輕量級(jí)鎖、重量級(jí)鎖崎溃?
  • JDK6 之后蜻直,不斷優(yōu)化 synchronized,提供了三種鎖的實(shí)現(xiàn)袁串,分別是偏向鎖概而、輕量級(jí)鎖、重量級(jí)鎖囱修,還提供自動(dòng)的升級(jí)和降級(jí)機(jī)制赎瑰。對(duì)于不同的競(jìng)爭(zhēng)情況,會(huì)自動(dòng)切換到合適的鎖實(shí)現(xiàn)破镰。當(dāng)沒有競(jìng)爭(zhēng)出現(xiàn)時(shí)餐曼,默認(rèn)使用偏斜鎖压储,也即是在對(duì)象頭的 Mark Word 部分設(shè)置線程ID,來表示鎖對(duì)象偏向的線程源譬,但這并不是互斥鎖集惋;當(dāng)有其他線程試圖鎖定某個(gè)已被偏斜過的鎖對(duì)象,JVM 就撤銷偏斜鎖踩娘,切換到輕量級(jí)鎖刮刑,輕量級(jí)鎖依賴 CAS 操作對(duì)象頭的 Mark Word 來試圖獲取鎖,如果重試成功养渴,就使用普通的輕量級(jí)鎖雷绢;否則進(jìn)一步升級(jí)為重量級(jí)鎖。鎖的降級(jí)發(fā)生在當(dāng) JVM 進(jìn)入安全點(diǎn)后理卑,檢查是否有閑置的鎖翘紊,并試圖進(jìn)行降級(jí)。鎖的升級(jí)和降級(jí)都是出于性能的考慮藐唠。
    偏向鎖:在線程競(jìng)爭(zhēng)不激烈的情況下帆疟,減少加鎖和解鎖的性能損耗,在對(duì)象頭中保存獲得鎖的線程ID信息中捆,如果這個(gè)線程再次請(qǐng)求鎖鸯匹,就用對(duì)象頭中保存的ID和自身線程ID對(duì)比坊饶,如果相同泄伪,就說明這個(gè)線程獲取鎖成功,不用再進(jìn)行加解鎖操作了匿级,省去了再次同步判斷的步驟蟋滴,提升了性能。

  • 輕量級(jí)鎖:再線程競(jìng)爭(zhēng)比偏向鎖更激烈的情況下痘绎,在線程的棧內(nèi)存中分配一段空間作為鎖的記錄空間(輕量級(jí)鎖對(duì)應(yīng)的對(duì)象的對(duì)象頭字段的拷貝)津函,線程通過CAS競(jìng)爭(zhēng)輕量級(jí)鎖,試圖把對(duì)象的對(duì)象頭字段改成指向鎖記錄的空間孤页,如果成功就說明獲取輕量級(jí)鎖成功尔苦,如果失敗,則進(jìn)入自旋(一定次數(shù)的循環(huán)行施,避免線程直接進(jìn)入阻塞狀態(tài))試圖獲取鎖允坚,如果自旋到一定次數(shù)還不能獲取到鎖,則進(jìn)入重量級(jí)鎖蛾号。

  • 自旋鎖:獲取輕量級(jí)鎖失敗后稠项,避免線程直接進(jìn)入阻塞狀態(tài)而采取的循環(huán)一定次數(shù)去嘗試獲取鎖。(線程進(jìn)入阻塞狀態(tài)和非阻塞狀態(tài)都是涉及到系統(tǒng)層面的鲜结,需要在用戶態(tài)到內(nèi)核態(tài)之間切換展运,非常消耗系統(tǒng)資源)實(shí)驗(yàn)證明活逆,鎖的持有時(shí)間一般是非常短的,所以一般多次嘗試就能競(jìng)爭(zhēng)到鎖拗胜。

  • 重量級(jí)鎖:在 JVM 中又叫做對(duì)象監(jiān)視器(monitor)蔗候,鎖對(duì)象的對(duì)象頭字段指向的是一個(gè)互斥量,多個(gè)線程競(jìng)爭(zhēng)鎖埂软,競(jìng)爭(zhēng)失敗的線程進(jìn)入阻塞狀態(tài)(操作系統(tǒng)層面)琴庵,并在鎖對(duì)象的一個(gè)等待池中等待被喚醒,被喚醒后的線程再次競(jìng)爭(zhēng)鎖資源仰美。

ReentrantLock詳解

  • 1.可重入鎖:可重入鎖是指同一個(gè)線程可以多次獲得同一把鎖迷殿;ReentrantLock和關(guān)鍵字Synchronized都是可重入鎖
  • 2.可中斷鎖:可中斷鎖時(shí)子線程在獲取鎖的過程中,是否可以相應(yīng)線程中斷操作咖杂。synchronized是不可中斷的庆寺,ReentrantLock是可中斷的
  • 3.公平鎖和非公平鎖:公平鎖是指多個(gè)線程嘗試獲取同一把鎖的時(shí)候,獲取鎖的順序按照線程到達(dá)的先后順序獲取诉字,而不是隨機(jī)插隊(duì)的方式獲取懦尝。synchronized是非公平鎖,而ReentrantLock是兩種都可以實(shí)現(xiàn)壤圃,不過默認(rèn)是非公平鎖
ReentrantLock的基本使用
public class Demo3 {

    private static int num = 0;

    private static ReentrantLock lock = new ReentrantLock();

    private static void add() {
        lock.lock();
        try {
            num++;
        } finally {
//注意lock.unlock()一定要放在finally中陵霉,否則,
//若程序出現(xiàn)了異常伍绳,鎖沒有釋放踊挠,那么其他線程就再也沒有機(jī)會(huì)獲取這個(gè)鎖了。
            lock.unlock();
        }

    }

    public static class T extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                Demo3.add();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        T t1 = new T();
        T t2 = new T();
        T t3 = new T();

        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();

        System.out.println(Demo3.num);
    }
}
ReentrantLock的使用過程:

1.創(chuàng)建鎖:ReentrantLock lock = new ReentrantLock();
2.獲取鎖:lock.lock()
3.釋放鎖:lock.unlock();

ReentrantLock的方法
無參構(gòu)造器(默認(rèn)為非公平鎖)
public ReentrantLock() {
        sync = new NonfairSync();//默認(rèn)是非公平的
    }
帶布爾值的構(gòu)造器(是否公平)

public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();//fair為true冲杀,公平鎖效床;反之,非公平鎖
    }
Sync的lock方法是抽象的权谁,實(shí)際的lock會(huì)代理到FairSync或是NonFairSync上(根據(jù)用戶的選擇來決定剩檀,公平鎖還是非公平鎖)
public void lock() {
        sync.lock();//代理到Sync的lock方法上
    }
  
此方法響應(yīng)中斷,當(dāng)線程在阻塞中的時(shí)候旺芽,若被中斷沪猴,會(huì)拋出InterruptedException異常 
public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);//代理到sync的相應(yīng)方法上,同lock方法的區(qū)別是此方法響應(yīng)中斷
    }
tryLock采章,嘗試獲取鎖运嗜,成功則直接返回true,不成功也不耽擱時(shí)間共缕,立即返回false洗出。
public boolean tryLock() {
        return sync.nonfairTryAcquire(1);//代理到sync的相應(yīng)方法上
    }
ReentrantLock的實(shí)現(xiàn)原理:
  • 它的設(shè)計(jì)基于AQS框架
  • 在 Lock 中,用到了一個(gè)同步隊(duì)列 AQS图谷,全稱 AbstractQueuedSynchronizer翩活,它 是一個(gè)同步工具也是Lock用來實(shí)現(xiàn)線程同步的核心組件阱洪。如果你搞懂了AQS,那 么J.U.C中絕大部分的工具都能輕松掌握

AQS 的兩種功能

  • 從使用層面來說菠镇,AQS的功能分為兩種:獨(dú)占和共享
  • 獨(dú)占鎖(寫鎖)冗荸,每次只能有一個(gè)線程持有鎖,比如前面給大家演示的ReentrantLock就是 以獨(dú)占方式實(shí)現(xiàn)的互斥鎖
  • 共 享 鎖(讀鎖) 利耍, 允 許 多 個(gè) 線 程 同 時(shí) 獲 取 鎖 蚌本, 并 發(fā) 訪 問 共 享 資 源 , 比 如 ReentrantReadWriteLock

AQS 的內(nèi)部實(shí)現(xiàn)

  • AQS 隊(duì)列內(nèi)部維護(hù)的是一個(gè) FIFO (先進(jìn)先出)的雙向鏈表隘梨,這種結(jié)構(gòu)的特點(diǎn)是每個(gè)數(shù)據(jù)結(jié)構(gòu)都有兩個(gè)指針程癌,分別指向直接的后繼節(jié)點(diǎn)和直接前驅(qū)節(jié)點(diǎn)。
  • 所以雙向鏈表可以從任 意一個(gè)節(jié)點(diǎn)開始很方便的訪問前驅(qū)和后繼轴猎。每個(gè) Node 其實(shí)是由線程封裝嵌莉,當(dāng)線 程爭(zhēng)搶鎖失敗后會(huì)封裝成Node加入到ASQ隊(duì)列中去;當(dāng)獲取鎖的線程釋放鎖以 后捻脖,會(huì)從隊(duì)列中喚醒一個(gè)阻塞的節(jié)點(diǎn)(線程)锐峭。


    image.png
從源碼看ReentrantLock非公平鎖實(shí)現(xiàn)原理

簡(jiǎn)單總結(jié)下流程:

1.先獲取state值,若為0可婶,意味著此時(shí)沒有線程獲取到資源沿癞,CAS將其設(shè)置為1,設(shè)置成功則代表獲取到排他鎖了矛渴;
2.若state大于0椎扬,肯定有線程已經(jīng)搶占到資源了,此時(shí)再去判斷是否就是自己搶占的曙旭,是的話盗舰,state累加晶府,返回true桂躏,重入成功,state的值即是線程重入的次數(shù)川陆;
3.其他情況剂习,則獲取鎖失敗。

NonFairSync(非公平可重入鎖)
static final class NonfairSync extends Sync {//繼承Sync
        private static final long serialVersionUID = 7316153563782823691L;
        /** 獲取鎖 */
        final void lock() {
            if (compareAndSetState(0, 1))//CAS設(shè)置state狀態(tài)较沪,若原值是0鳞绕,將其置為1
                setExclusiveOwnerThread(Thread.currentThread());//將當(dāng)前線程標(biāo)記為已持有鎖
            else
                acquire(1);//若設(shè)置失敗,調(diào)用AQS的acquire方法尸曼,acquire又會(huì)調(diào)用我們下面重寫的tryAcquire方法们何。這里說的調(diào)用失敗有兩種情況:1當(dāng)前沒有線程獲取到資源,state為0控轿,但是將state由0設(shè)置為1的時(shí)候冤竹,其他線程搶占資源拂封,將state修改了,導(dǎo)致了CAS失旔腥洹冒签;2 state原本就不為0,也就是已經(jīng)有線程獲取到資源了钟病,有可能是別的線程獲取到資源萧恕,也有可能是當(dāng)前線程獲取的,這時(shí)線程又重復(fù)去獲取肠阱,所以去tryAcquire中的nonfairTryAcquire我們應(yīng)該就能看到可重入的實(shí)現(xiàn)邏輯了票唆。
        }
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);//調(diào)用Sync中的方法
        }
    }


nonfairTryAcquire()
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//獲取當(dāng)前線程
            int c = getState();//獲取當(dāng)前state值
            if (c == 0) {//若state為0,意味著沒有線程獲取到資源屹徘,CAS將state設(shè)置為1惰说,并將當(dāng)前線程標(biāo)記我獲取到排他鎖的線程,返回true
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//若state不為0缘回,但是持有鎖的線程是當(dāng)前線程
                int nextc = c + acquires;//state累加1
                if (nextc < 0) // int類型溢出了
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);//設(shè)置state吆视,此時(shí)state大于1,代表著一個(gè)線程多次獲鎖酥宴,state的值即是線程重入的次數(shù)
                return true;//返回true啦吧,獲取鎖成功
            }
            return false;//獲取鎖失敗了
        }
從源碼看ReentrantLock公平鎖實(shí)現(xiàn)原理
FairSync

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);//直接調(diào)用AQS的模板方法acquire,acquire會(huì)調(diào)用下面我們重寫的這個(gè)tryAcquire
        }

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//獲取當(dāng)前線程
            int c = getState();//獲取state值
            if (c == 0) {//若state為0拙寡,意味著當(dāng)前沒有線程獲取到資源授滓,那就可以直接獲取資源了嗎?NO!這不就跟之前的非公平鎖的邏輯一樣了嘛肆糕“愣眩看下面的邏輯
                if (!hasQueuedPredecessors() &&//判斷在時(shí)間順序上,是否有申請(qǐng)鎖排在自己之前的線程诚啃,若沒有淮摔,才能去獲取,CAS設(shè)置state始赎,并標(biāo)記當(dāng)前線程為持有排他鎖的線程和橙;反之,不能獲仍於狻魔招!這即是公平的處理方式。
                    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;
        }
    }

可以看到,公平鎖的大致邏輯與非公平鎖是一致的杆逗,不同的地方在于有了!hasQueuedPredecessors()這個(gè)判斷邏輯乡翅,即便state為0吁讨,也不能貿(mào)然直接去獲取,要先去看有沒有還在排隊(duì)的線程峦朗,若沒有建丧,才能嘗試去獲取,做后面的處理波势。反之翎朱,返回false,獲取失敗尺铣。

看看這個(gè)判斷是否有排隊(duì)中線程的邏輯

hasQueuedPredecessors()
public final boolean hasQueuedPredecessors() {
        Node t = tail; // 尾結(jié)點(diǎn)
        Node h = head;//頭結(jié)點(diǎn)
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());//判斷是否有排在自己之前的線程
    }
注意
  • 1.ReentrantLock可以實(shí)現(xiàn)公平鎖和非公平鎖
  • 2.ReentrantLock默認(rèn)實(shí)現(xiàn)的是非公平鎖
  • 3.ReentrantLock的獲取鎖和釋放鎖必須成對(duì)出現(xiàn)拴曲,鎖了幾次,也要釋放幾次
  • 4.釋放鎖的操作必須放在finally中執(zhí)行
  • 5.lockInterruptibly()實(shí)例方法可以相應(yīng)線程的中斷方法凛忿,調(diào)用線程的- interrupt()方法時(shí)澈灼,lockInterruptibly()方法會(huì)觸發(fā)InterruptedException異常
  • 6.關(guān)于InterruptedException異常說一下,看到方法聲明上帶有 throws InterruptedException店溢,表示該方法可以相應(yīng)線程中斷叁熔,調(diào)用線程的interrupt()方法時(shí),這些方法會(huì)觸發(fā)InterruptedException異常床牧,觸發(fā)InterruptedException時(shí)荣回,線程的中斷中斷狀態(tài)會(huì)被清除。所以如果程序由于調(diào)用interrupt()方法而觸發(fā)InterruptedException異常戈咳,線程的標(biāo)志由默認(rèn)的false變?yōu)閠ure心软,然后又變?yōu)閒alse
  • 7.實(shí)例方法tryLock()會(huì)嘗試獲取鎖,會(huì)立即返回著蛙,返回值表示是否獲取成功
  • 8.實(shí)例方法tryLock(long timeout, TimeUnit unit)會(huì)在指定的時(shí)間內(nèi)嘗試獲取鎖删铃,指定的時(shí)間內(nèi)是否能夠獲取鎖,都會(huì)返回踏堡,返回值表示是否獲取鎖成功猎唁,該方法會(huì)響應(yīng)線程的中斷
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市暂吉,隨后出現(xiàn)的幾起案子胖秒,更是在濱河造成了極大的恐慌,老刑警劉巖慕的,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異挤渔,居然都是意外死亡肮街,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門判导,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嫉父,“玉大人沛硅,你說我怎么就攤上這事∪葡剑” “怎么了摇肌?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)仪际。 經(jīng)常有香客問我围小,道長(zhǎng),這世上最難降的妖魔是什么树碱? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任肯适,我火速辦了婚禮,結(jié)果婚禮上成榜,老公的妹妹穿的比我還像新娘框舔。我一直安慰自己,他們只是感情好赎婚,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布刘绣。 她就那樣靜靜地躺著,像睡著了一般挣输。 火紅的嫁衣襯著肌膚如雪额港。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天歧焦,我揣著相機(jī)與錄音移斩,去河邊找鬼。 笑死绢馍,一個(gè)胖子當(dāng)著我的面吹牛向瓷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播舰涌,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼猖任,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了瓷耙?” 一聲冷哼從身側(cè)響起朱躺,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎搁痛,沒想到半個(gè)月后长搀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鸡典,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年源请,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谁尸,死狀恐怖舅踪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情良蛮,我是刑警寧澤抽碌,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站决瞳,受9級(jí)特大地震影響货徙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瞒斩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一破婆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧胸囱,春花似錦祷舀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谤职,卻和暖如春饰豺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背允蜈。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工冤吨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饶套。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓漩蟆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親妓蛮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子怠李,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容