synchronized,ReentrantLock解决锁冲突,脏读的问题
最常見(jiàn)的秒殺系統(tǒng),解決思路就是從前端、后臺(tái)服務(wù)、數(shù)據(jù)庫(kù)層層去掉負(fù)載,以達(dá)到平衡
鎖作為并發(fā)共享數(shù)據(jù),保證一致性的工具,在JAVA平臺(tái)有多種實(shí)現(xiàn)(如 synchronized 和 ReentrantLock等等 ) 。這些已經(jīng)寫(xiě)好提供的鎖為我們開(kāi)發(fā)提供了便利,但是鎖的具體性質(zhì)以及類型卻很少被提及。本系列文章將分析JAVA下常見(jiàn)的鎖名稱以及特性,為大家答疑解惑。
public class Thread1 implements Runnable {private String flag = "start";private String control = "";public void run() {// TODO Auto-generated method stubint i = 0;while (true) {if (flag.equals("start")) {i++;System.out.println("The thread1 is running" + i);} else if (flag.equals("wait")) {try {System.out.println("===wait===");synchronized (control) {control.wait();}} catch (InterruptedException e) {// TODO Auto-generated catch block e.printStackTrace();}}}}public void wait1() {this.flag = "wait";}public void start1() {this.flag = "start";if (flag.equals("start")) {synchronized (control) {control.notifyAll();}}}}看調(diào)用
public static void main(String[] args) {// TODO Auto-generated method stubThread1 th1 = new Thread1();Thread t1 = new Thread(th1);t1.start();try {Thread.sleep(20);} catch (InterruptedException e) {// TODO Auto-generated catch block e.printStackTrace();}//thread線程暫停 th1.wait1();try {Thread.sleep(2000);} catch (InterruptedException e) {// TODO Auto-generated catch block e.printStackTrace();}//thread線程繼續(xù)運(yùn)行 th1.start1();//th1.wait1();//th1.start1();}ReentrantLock和synchronized都是可重入鎖
可重入鎖,也叫做遞歸鎖,指的是同一線程 外層函數(shù)獲得鎖之后 ,內(nèi)層遞歸函數(shù)仍然有獲取該鎖的代碼,但不受影響。
廣義上的可重入鎖指的是可重復(fù)可遞歸調(diào)用的鎖,在外層使用鎖之后,在內(nèi)層仍然可以使用,并且不發(fā)生死鎖(前提得是同一個(gè)對(duì)象或者class),這樣的鎖就叫做可重入鎖。
public class ReentrantTest implements Runnable {
? ? public synchronized void get() {
? ? ? ? System.out.println(Thread.currentThread().getName());
? ? ? ? set();
? ? }
? ? public synchronized void set() {
? ? ? ? System.out.println(Thread.currentThread().getName());
? ? }
? ? public void run() {
? ? ? ? get();
? ? }
? ? public static void main(String[] args) {
? ? ? ? ReentrantTest rt = new ReentrantTest();
? ? ? ? for(;;){
? ? ? ? ? ? new Thread(rt).start();
? ? ? ? }
輸出
Thread-8492
Thread-8492
Thread-8494
Thread-8494
Thread-8495
Thread-8495
Thread-8493
Thread-8493
?set()和get()同時(shí)輸出了線程名稱,表明即使遞歸使用synchronized也沒(méi)有發(fā)生死鎖,證明其是可重入的。
?不可重入鎖,與可重入鎖相反,不可遞歸調(diào)用,遞歸調(diào)用就發(fā)生死鎖。
package com.thread;import java.util.concurrent.atomic.AtomicReference;public class UnreentrantLock {private AtomicReference<Thread> owner = new AtomicReference<Thread>();//記錄當(dāng)前鎖的持有線程對(duì)象public void lock() {//加鎖Thread current = Thread.currentThread();//獲取當(dāng)前線程對(duì)象for (; ; ) {//自旋(被當(dāng)前線程或其他線程持有鎖,就會(huì)循環(huán)) for 的三種用法,class、增強(qiáng)型循環(huán)、無(wú)線循環(huán)if (owner.compareAndSet(null, current)) {//只有鎖可用即為null,才能設(shè)置當(dāng)前線程為鎖持有對(duì)象,并返回truereturn;}}}public void unlock() {//解鎖Thread current = Thread.currentThread();//獲取當(dāng)前線程對(duì)象owner.compareAndSet(current, null);//設(shè)置鎖的持有對(duì)象為null } }使用原子引用來(lái)存放線程,同一線程兩次調(diào)用lock()方法,如果不執(zhí)行unlock()釋放鎖的話,第二次調(diào)用自旋的時(shí)候就會(huì)產(chǎn)生死鎖,這個(gè)鎖就不是可重入的。
? ? 實(shí)際上同一個(gè)線程不必每次都去釋放鎖再來(lái)獲取鎖,這樣的調(diào)度切換是很耗資源的。稍微改一下,把它變成一個(gè)可重入鎖:
package com.thread;import java.util.concurrent.atomic.AtomicReference;public class UnreentrantLock {private AtomicReference<Thread> owner = new AtomicReference<Thread>();//記錄當(dāng)前鎖的持有線程對(duì)象private int state = 0;//記錄重入次數(shù)public void lock() {//加鎖Thread current = Thread.currentThread();//獲取當(dāng)前線程對(duì)象if (owner.compareAndSet(null, current)) {//當(dāng)前鎖可用state = 1;//狀態(tài)置為1return;} else {if (current == owner.get()) {//如果當(dāng)前線程持有鎖state++;//重入次數(shù)加1return;}for (; ; ) {//被其他線程持有就會(huì)繼續(xù)循環(huán)if (owner.compareAndSet(null, current)) {//只有鎖可用即為null,才能設(shè)置當(dāng)前線程為鎖持有對(duì)象,并返回truereturn;}}}}public void unlock() {//解鎖Thread current = Thread.currentThread();//獲取當(dāng)前線程對(duì)象if (current == owner.get()) {//如果當(dāng)前線程持有鎖if (state > 0) {//重入次數(shù)大于0state--;//重入次數(shù)減1} else {owner.compareAndSet(current, null);//設(shè)置鎖的持有對(duì)象為null }}} }? 在執(zhí)行每次操作之前,判斷當(dāng)前鎖持有者是否是當(dāng)前對(duì)象,采用state計(jì)數(shù),不用每次去釋放鎖。
?
ReentrantLock原理
- 原子狀態(tài):原子狀態(tài)有 CAS(compareAndSetState) 操作來(lái)存儲(chǔ)當(dāng)前鎖的狀態(tài),判斷鎖是否有其他線程持有。
- 等待隊(duì)列:所有沒(méi)有請(qǐng)求到鎖的線程,會(huì)進(jìn)入等待隊(duì)列進(jìn)行等待。待有線程釋放鎖后,系統(tǒng)才能夠從等待隊(duì)列中喚醒一個(gè)線程,繼續(xù)工作。詳見(jiàn):隊(duì)列同步器——AQS
- 阻塞原語(yǔ) park() 和 unpark(),用來(lái)掛起和恢復(fù)線程。沒(méi)有得到鎖的線程將會(huì)被掛起。關(guān)于阻塞原語(yǔ),詳見(jiàn):線程阻塞工具類——LockSupport
ReentrantLock的幾個(gè)重要方法整理如下:
- lock():獲得鎖,如果鎖被占用,進(jìn)入等待。
- lockInterruptibly():獲得鎖,但優(yōu)先響應(yīng)中斷。
- tryLock():嘗試獲得鎖,如果成功,立即放回 true,反之失敗返回 false。該方法不會(huì)進(jìn)行等待,立即返回。
- tryLock(long time, TimeUnit unit):在給定的時(shí)間內(nèi)嘗試獲得鎖。
unLock():釋放鎖。
一、何為重進(jìn)入(重入)?
重進(jìn)入是指任意線程在獲取到鎖之后能夠再次獲取該鎖而不會(huì)被鎖阻塞,該特性的實(shí)現(xiàn)需要解決以下兩個(gè)問(wèn)題:
-
- 線程再次獲取鎖:鎖需要去識(shí)別獲取鎖的線程是否為當(dāng)前占據(jù)鎖的線程,如果是,則再次成功獲取。
- 鎖的最終釋放。線程重復(fù) n 次獲取了鎖,隨后在第 n 次釋放該鎖后,其它線程能夠獲取到該鎖。鎖的最終釋放要求鎖對(duì)于獲取進(jìn)行計(jì)數(shù)自增,計(jì)數(shù)表示當(dāng)前鎖被重復(fù)獲取的次數(shù),而鎖被釋放時(shí),計(jì)數(shù)自減,當(dāng)計(jì)數(shù)等于 0 時(shí)表示鎖已經(jīng)成功釋放。
acquireQueued 方法增加了再次獲取同步狀態(tài)的處理邏輯:通過(guò)判斷當(dāng)前線程是否為獲取鎖的線程,來(lái)決定獲取操作是否成功,如果獲取鎖的線程再次請(qǐng)求,則將同步狀態(tài)值進(jìn)行增加并返回 true,表示獲取同步狀態(tài)成功。
成功獲取鎖的線程再次獲取鎖,只是增加了同步狀態(tài)值,也就是要求 ReentrantLock 在釋放同步狀態(tài)時(shí)減少同步狀態(tài)值,釋放鎖源碼如下:
如果鎖被獲取 n 次,那么前 (n-1) 次 tryRelease(int releases) 方法必須返回 false,只有同步狀態(tài)完全釋放了,才能返回 true。該方法將同步狀態(tài)是否為 0 作為最終釋放的條件,當(dāng)同步狀態(tài)為 0 時(shí),將占有線程設(shè)置為 null,并返回 true,表示釋放成功。
通過(guò)對(duì)獲取與釋放的分析,就可以解釋,以上兩個(gè)例子中出現(xiàn)的兩個(gè)問(wèn)題:為什么 ReentrantLock 鎖能夠支持一個(gè)線程對(duì)資源的重復(fù)加鎖?為什么公平鎖例子中出現(xiàn),公平鎖線程是不斷切換的,而非公平鎖出現(xiàn)同一線程連續(xù)獲取鎖的情況?
- 為什么支持重復(fù)加鎖?因?yàn)樵创a中用變量 c 來(lái)保存當(dāng)前鎖被獲取了多少次,故在釋放時(shí),對(duì) c 變量進(jìn)行減操作,只有 c 變量為 0 時(shí),才算鎖的最終釋放。所以可以 lock 多次,同時(shí) unlock 也必須與 lock 同樣的次數(shù)。
- 為什么非公平鎖出現(xiàn)同一線程連續(xù)獲取鎖的情況?tryAcquire 方法中增加了再次獲取同步狀態(tài)的處理邏輯;
二、為什么使用可重入鎖?
ReentrantLock 是一個(gè)可重入的互斥(/獨(dú)占)鎖,又稱為“獨(dú)占鎖”。
ReentrantLock通過(guò)自定義隊(duì)列同步器(AQS-AbstractQueuedSychronized,是實(shí)現(xiàn)鎖的關(guān)鍵)來(lái)實(shí)現(xiàn)鎖的獲取與釋放。
其可以完全替代 synchronized 關(guān)鍵字。JDK 5.0 早期版本,其性能遠(yuǎn)好于 synchronized,但 JDK 6.0 開(kāi)始,JDK 對(duì) synchronized 做了大量的優(yōu)化,使得兩者差距并不大。
“獨(dú)占”,就是在同一時(shí)刻只能有一個(gè)線程獲取到鎖,而其它獲取鎖的線程只能處于同步隊(duì)列中等待,只有獲取鎖的線程釋放了鎖,后繼的線程才能夠獲取鎖。
“可重入”,就是支持重進(jìn)入的鎖,它表示該鎖能夠支持一個(gè)線程對(duì)資源的重復(fù)加鎖。
該鎖還支持獲取鎖時(shí)的公平和非公平性選擇。“公平”是指“不同的線程獲取鎖的機(jī)制是公平的”,而“不公平”是指“不同的線程獲取鎖的機(jī)制是非公平的”。
1?中斷響應(yīng)(lockInterruptibly)
對(duì)于 synchronized 來(lái)說(shuō),如果一個(gè)線程在等待鎖,那么結(jié)果只有兩種情況,獲得這把鎖繼續(xù)執(zhí)行,或者線程就保持等待。
而使用重入鎖,提供了另一種可能,這就是線程可以被中斷。也就是在等待鎖的過(guò)程中,程序可以根據(jù)需要取消對(duì)鎖的需求。
下面的例子中,產(chǎn)生了死鎖,但得益于鎖中斷,最終解決了這個(gè)死鎖:
public class IntLock implements Runnable{public static ReentrantLock lock1 = new ReentrantLock();public static ReentrantLock lock2 = new ReentrantLock();int lock;/*** 控制加鎖順序,產(chǎn)生死鎖*/public IntLock(int lock) {this.lock = lock;}public void run() {try {if (lock == 1) {lock1.lockInterruptibly(); // 如果當(dāng)前線程未被 中斷,則獲取鎖。try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}lock2.lockInterruptibly();System.out.println(Thread.currentThread().getName()+",執(zhí)行完畢!");} else {lock2.lockInterruptibly();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}lock1.lockInterruptibly();System.out.println(Thread.currentThread().getName()+",執(zhí)行完畢!");}} catch (InterruptedException e) {e.printStackTrace();} finally {// 查詢當(dāng)前線程是否保持此鎖。if (lock1.isHeldByCurrentThread()) {lock1.unlock();}if (lock2.isHeldByCurrentThread()) {lock2.unlock();}System.out.println(Thread.currentThread().getName() + ",退出。");}}public static void main(String[] args) throws InterruptedException {IntLock intLock1 = new IntLock(1);IntLock intLock2 = new IntLock(2);Thread thread1 = new Thread(intLock1, "線程1");Thread thread2 = new Thread(intLock2, "線程2");thread1.start();thread2.start();Thread.sleep(1000);thread2.interrupt(); // 中斷線程2 } }上述例子中,線程 thread1 和 thread2 啟動(dòng)后,thread1 先占用 lock1,再占用 lock2;thread2 反之,先占 lock2,后占 lock1。這便形成 thread1 和 thread2 之間的相互等待。
代碼 56 行,main 線程處于休眠(sleep)狀態(tài),兩線程此時(shí)處于死鎖的狀態(tài),代碼 57 行 thread2 被中斷(interrupt),故 thread2 會(huì)放棄對(duì) lock1 的申請(qǐng),同時(shí)釋放已獲得的 lock2。這個(gè)操作導(dǎo)致 thread1 順利獲得 lock2,從而繼續(xù)執(zhí)行下去。
執(zhí)行代碼,輸出如下:
2鎖申請(qǐng)等待限時(shí)(tryLock)
除了等待外部通知(中斷操作 interrupt )之外,限時(shí)等待也可以做到避免死鎖。
通常,無(wú)法判斷為什么一個(gè)線程遲遲拿不到鎖。也許是因?yàn)楫a(chǎn)生了死鎖,也許是產(chǎn)生了饑餓。但如果給定一個(gè)等待時(shí)間,讓線程自動(dòng)放棄,那么對(duì)系統(tǒng)來(lái)說(shuō)是有意義的??梢允褂?tryLock() 方法進(jìn)行一次限時(shí)的等待。
?
public class TimeLock implements Runnable{public static ReentrantLock lock = new ReentrantLock();public void run() {try {if (lock.tryLock(5, TimeUnit.SECONDS)) {Thread.sleep(6 * 1000);}else {System.out.println(Thread.currentThread().getName()+" get Lock Failed");}} catch (InterruptedException e) {e.printStackTrace();}finally {// 查詢當(dāng)前線程是否保持此鎖。if (lock.isHeldByCurrentThread()) {System.out.println(Thread.currentThread().getName()+" release lock");lock.unlock();}}}/*** 在本例中,由于占用鎖的線程會(huì)持有鎖長(zhǎng)達(dá)6秒,故另一個(gè)線程無(wú)法再5秒的等待時(shí)間內(nèi)獲得鎖,因此請(qǐng)求鎖會(huì)失敗。*/public static void main(String[] args) {TimeLock timeLock = new TimeLock();Thread t1 = new Thread(timeLock, "線程1");Thread t2 = new Thread(timeLock, "線程2");t1.start();t2.start();} }上述例子中,由于占用鎖的線程會(huì)持有鎖長(zhǎng)達(dá) 6 秒,故另一個(gè)線程無(wú)法在 5 秒的等待時(shí)間內(nèi)獲得鎖,因此,請(qǐng)求鎖失敗。
ReentrantLock.tryLock()方法也可以不帶參數(shù)直接運(yùn)行。這種情況下,當(dāng)前線程會(huì)嘗試獲得鎖,如果鎖并未被其他線程占用,則申請(qǐng)鎖成功,立即返回 true。否則,申請(qǐng)失敗,立即返回 false,當(dāng)前線程不會(huì)進(jìn)行等待。這種模式不會(huì)引起線程等待,因此也不會(huì)產(chǎn)生死鎖。
3?公平鎖
·默認(rèn)情況下,鎖的申請(qǐng)都是非公平的。也就是說(shuō),如果線程 1 與線程 2,都申請(qǐng)獲得鎖 A,那么誰(shuí)獲得鎖不是一定的,是由系統(tǒng)在等待隊(duì)列中隨機(jī)挑選的。這就好比,買票的人不排隊(duì),售票姐姐只能隨機(jī)挑一個(gè)人賣給他,這顯然是不公平的。而公平鎖,它會(huì)按照時(shí)間的先后順序,保證先到先得。公平鎖的特點(diǎn)是:不會(huì)產(chǎn)生饑餓現(xiàn)象。
重入鎖允許對(duì)其公平性進(jìn)行設(shè)置。構(gòu)造函數(shù)如下:
public ReentrantLock(boolean fair) public class FairLock implements Runnable{public static ReentrantLock fairLock = new ReentrantLock(true);public void run() {while (true) {try {fairLock.lock();System.out.println(Thread.currentThread().getName()+",獲得鎖!");}finally {fairLock.unlock();}}}public static void main(String[] args) {FairLock fairLock = new FairLock();Thread t1 = new Thread(fairLock, "線程1");Thread t2 = new Thread(fairLock, "線程2");t1.start();t2.start();} }測(cè)試結(jié)果:
1.當(dāng)參數(shù)設(shè)置為 true 時(shí):線程1 和 線程2 交替進(jìn)行 公平競(jìng)爭(zhēng) 交替打印
線程1,獲得鎖! 線程2,獲得鎖! 線程1,獲得鎖! 線程2,獲得鎖! 線程1,獲得鎖! 線程2,獲得鎖! 線程1,獲得鎖! 線程2,獲得鎖! 線程1,獲得鎖! 線程2,獲得鎖! 線程1,獲得鎖! 線程2,獲得鎖! 線程1,獲得鎖! 線程2,獲得鎖! 線程1,獲得鎖!2.當(dāng)參數(shù)設(shè)置為 false 時(shí): 此時(shí)可以看到線程1 可以持續(xù)拿到鎖 等線程1 執(zhí)行完后 線程2 才可以拿到線程 然后多次執(zhí)行 ; 這就是使用 可重入鎖后 是非公平機(jī)制 線程可以優(yōu)先多次拿到執(zhí)行權(quán) 線程1,獲得鎖! 線程1,獲得鎖! 線程1,獲得鎖! 線程1,獲得鎖! 線程1,獲得鎖! 線程1,獲得鎖! 線程1,獲得鎖! 線程1,獲得鎖! 線程1,獲得鎖! 線程1,獲得鎖! 線程1,獲得鎖! 線程1,獲得鎖! 線程2,獲得鎖! 線程2,獲得鎖! 線程2,獲得鎖! 線程2,獲得鎖!
修改重入鎖是否公平,觀察輸出結(jié)果,如果公平,輸出結(jié)果始終為兩個(gè)線程交替的獲得鎖,如果是非公平,輸出結(jié)果為一個(gè)線程占用鎖很長(zhǎng)時(shí)間,然后才會(huì)釋放鎖,另個(gè)線程才能執(zhí)行。
ReenTrantLock可重入鎖(和synchronized的區(qū)別)總結(jié)
可重入性:
從名字上理解,ReenTrantLock的字面意思就是再進(jìn)入的鎖,其實(shí)synchronized關(guān)鍵字所使用的鎖也是可重入的,兩者關(guān)于這個(gè)的區(qū)別不大。兩者都是同一個(gè)線程沒(méi)進(jìn)入一次,鎖的計(jì)數(shù)器都自增1,所以要等到鎖的計(jì)數(shù)器下降為0時(shí)才能釋放鎖。
鎖的實(shí)現(xiàn):
Synchronized是依賴于JVM實(shí)現(xiàn)的,而ReenTrantLock是JDK實(shí)現(xiàn)的,有什么區(qū)別,說(shuō)白了就類似于操作系統(tǒng)來(lái)控制實(shí)現(xiàn)和用戶自己敲代碼實(shí)現(xiàn)的區(qū)別。前者的實(shí)現(xiàn)是比較難見(jiàn)到的,后者有直接的源碼可供閱讀。
性能的區(qū)別:
在Synchronized優(yōu)化以前,synchronized的性能是比ReenTrantLock差很多的,但是自從Synchronized引入了偏向鎖,輕量級(jí)鎖(自旋鎖)后,兩者的性能就差不多了,在兩種方法都可用的情況下,官方甚至建議使用synchronized,其實(shí)synchronized的優(yōu)化我感覺(jué)就借鑒了ReenTrantLock中的CAS技術(shù)。都是試圖在用戶態(tài)就把加鎖問(wèn)題解決,避免進(jìn)入內(nèi)核態(tài)的線程阻塞。
功能區(qū)別:
便利性:很明顯Synchronized的使用比較方便簡(jiǎn)潔,并且由編譯器去保證鎖的加鎖和釋放,而ReenTrantLock需要手工聲明來(lái)加鎖和釋放鎖,為了避免忘記手工釋放鎖造成死鎖,所以最好在finally中聲明釋放鎖。
鎖的細(xì)粒度和靈活度:很明顯ReenTrantLock優(yōu)于Synchronized
ReenTrantLock獨(dú)有的能力:
1.??????ReenTrantLock可以指定是公平鎖還是非公平鎖。而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的線程先獲得鎖。
2.??????ReenTrantLock提供了一個(gè)Condition(條件)類,用來(lái)實(shí)現(xiàn)分組喚醒需要喚醒的線程們,而不是像synchronized要么隨機(jī)喚醒一個(gè)線程要么喚醒全部線程。
3.??????ReenTrantLock提供了一種能夠中斷等待鎖的線程的機(jī)制,通過(guò)lock.lockInterruptibly()來(lái)實(shí)現(xiàn)這個(gè)機(jī)制。
ReenTrantLock實(shí)現(xiàn)的原理:
在網(wǎng)上看到相關(guān)的源碼分析,本來(lái)這塊應(yīng)該是本文的核心,但是感覺(jué)比較復(fù)雜就不一一詳解了,簡(jiǎn)單來(lái)說(shuō),ReenTrantLock的實(shí)現(xiàn)是一種自旋鎖,通過(guò)循環(huán)調(diào)用CAS操作來(lái)實(shí)現(xiàn)加鎖。它的性能比較好也是因?yàn)楸苊饬耸咕€程進(jìn)入內(nèi)核態(tài)的阻塞狀態(tài)。想盡辦法避免線程進(jìn)入內(nèi)核的阻塞狀態(tài)是我們?nèi)シ治龊屠斫怄i設(shè)計(jì)的關(guān)鍵鑰匙。
什么情況下使用ReenTrantLock:
答案是,如果你需要實(shí)現(xiàn)ReenTrantLock的三個(gè)獨(dú)有功能時(shí)。
?
公平與非公平唯一的區(qū)別是判斷條件中多了hasQueuedPredecessors()方法,即加入了同步隊(duì)列中當(dāng)前節(jié)點(diǎn)是否有前驅(qū)節(jié)點(diǎn)的判斷,如果該方法返回了true,則表示有線程比當(dāng)前線程更早地請(qǐng)求獲取鎖,所以需要等待前驅(qū)線程獲取并釋放鎖后才能繼續(xù)獲取該鎖。
但是非公平鎖是默認(rèn)實(shí)現(xiàn):非公平性鎖可能使線程“饑餓”,但是極少的線程切換,可以保證其更大的吞吐量。而公平性鎖,保證了鎖的獲取按照FIFO原則,代價(jià)是進(jìn)行大量的線程切換。
synchronized可重入性
同一線程在調(diào)用自己類中其他synchronized方法/塊或調(diào)用父類的synchronized方法/塊都不會(huì)阻礙該線程的執(zhí)行,就是說(shuō)同一線程對(duì)同一個(gè)對(duì)象鎖是可重入的,而且同一個(gè)線程可以獲取同一把鎖多次,也就是可以多次重入。
轉(zhuǎn)載于:https://www.cnblogs.com/ation/p/10905723.html
總結(jié)
以上是生活随笔為你收集整理的synchronized,ReentrantLock解决锁冲突,脏读的问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Set的常用实现类HashSet和Tre
- 下一篇: ip计算