到底什么是重入鎖?拜托,一次搞清楚!
JDK中獨占鎖的實現(xiàn)除了使用關(guān)鍵字synchronized外,還可以使用ReentrantLock。雖然在性能上ReentrantLock和synchronized沒有什么區(qū)別,但ReentrantLock相比synchronized而言功能更加豐富,使用起來更為靈活,也更適合復(fù)雜的并發(fā)場景。
兩者的相同點
ReentrantLock是獨占鎖且可重入的
例子
public?class?ReentrantLockTest?{
????public?static?void?main(String[]?args)?throws?InterruptedException?{
????????ReentrantLock?lock?=?new?ReentrantLock();
????????for?(int?i?=?1;?i?<=?3;?i++)?{
????????????lock.lock();
????????}
????????for(int?i=1;i<=3;i++){
????????????try?{
????????????}?finally?{
????????????????lock.unlock();
????????????}
????????}
????}
}
上面的代碼通過lock()方法先獲取鎖三次,然后通過unlock()方法釋放鎖3次,程序可以正常退出。從上面的例子可以看出,ReentrantLock是可以重入的鎖,當一個線程獲取鎖時,還可以接著重復(fù)獲取多次。在加上ReentrantLock的的獨占性,我們可以得出以下ReentrantLock和synchronized的相同點。
ReentrantLock和synchronized都是獨占鎖,只允許線程互斥的訪問臨界區(qū)。但是實現(xiàn)上兩者不同:synchronized加鎖解鎖的過程是隱式的,用戶不用手動操作,優(yōu)點是操作簡單,但顯得不夠靈活。一般并發(fā)場景使用synchronized的就夠了;ReentrantLock需要手動加鎖和解鎖,且解鎖的操作盡量要放在finally代碼塊中,保證線程正確釋放鎖。ReentrantLock操作較為復(fù)雜,但是因為可以手動控制加鎖和解鎖過程,在復(fù)雜的并發(fā)場景中能派上用場。ReentrantLock和synchronized都是可重入的。synchronized因為可重入因此可以放在被遞歸執(zhí)行的方法上,且不用擔心線程最后能否正確釋放鎖;而ReentrantLock在重入時要確保保重復(fù)獲取鎖的次數(shù)必須和重復(fù)釋放鎖的次數(shù)一樣,否則可能導(dǎo)致其他線程無法獲得該鎖。
兩者的額外功能
ReentrantLock可以實現(xiàn)公平鎖
公平鎖是指當鎖可用時,在鎖上等待時間最長的線程將獲得鎖的使用權(quán)。而非公平鎖則隨機分配這種使用權(quán)。和synchronized一樣,默認的ReentrantLock實現(xiàn)是非公平鎖,因為相比公平鎖,非公平鎖性能更好。當然公平鎖能防止饑餓,某些情況下也很有用。在創(chuàng)建ReentrantLock的時候通過傳進參數(shù)true創(chuàng)建公平鎖,如果傳入的是false或沒傳參數(shù)則創(chuàng)建的是非公平鎖
ReentrantLock?lock?=?new?ReentrantLock(true);
繼續(xù)跟進看下源碼
/**
?*?Creates?an?instance?of?{@code?ReentrantLock}?with?the
?*?given?fairness?policy.
?*
?*?@param?fair?{@code?true}?if?this?lock?should?use?a?fair?ordering?policy
?*/
public?ReentrantLock(boolean?fair)?{
????sync?=?fair???new?FairSync()?:?new?NonfairSync();
}
可以看到公平鎖和非公平鎖的實現(xiàn)關(guān)鍵在于成員變量sync的實現(xiàn)不同,這是鎖實現(xiàn)互斥同步的核心。以后有機會我們再細講。
一個公平鎖的例子
public?class?ReentrantLockTest?{
????static?Lock?lock?=?new?ReentrantLock(true);
????public?static?void?main(String[]?args)?throws?InterruptedException?{
????????for(int?i=0;i<5;i++){
????????????new?Thread(new?ThreadDemo(i)).start();
????????}
????}
????static?class?ThreadDemo?implements?Runnable?{
????????Integer?id;
????????public?ThreadDemo(Integer?id)?{
????????????this.id?=?id;
????????}
????????@Override
??????public?void?run()?{
????????????try?{
????????????????TimeUnit.MILLISECONDS.sleep(10);
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}
????????????for(int?i=0;i<2;i++){
????????????????lock.lock();
????????????????System.out.println("獲得鎖的線程:"+id);
????????????????lock.unlock();
????????????}
????????}
????}
}
公平鎖結(jié)果

我們開啟5個線程,讓每個線程都獲取釋放鎖兩次。為了能更好的觀察到結(jié)果,在每次獲取鎖前讓線程休眠10毫秒??梢钥吹骄€程幾乎是輪流的獲取到了鎖。如果我們改成非公平鎖,再看下結(jié)果
非公平鎖結(jié)果

線程會重復(fù)獲取鎖。如果申請獲取鎖的線程足夠多,那么可能會造成某些線程長時間得不到鎖。這就是非公平鎖的“饑餓”問題。
公平鎖和非公平鎖該如何選擇?
大部分情況下我們使用非公平鎖,因為其性能比公平鎖好很多。
但是公平鎖能夠避免線程饑餓,某些情況下也很有用。
ReentrantLock可響應(yīng)中斷
當使用synchronized實現(xiàn)鎖時,阻塞在鎖上的線程除非獲得鎖否則將一直等待下去,也就是說這種無限等待獲取鎖的行為無法被中斷。而ReentrantLock給我們提供了一個可以響應(yīng)中斷的獲取鎖的方法lockInterruptibly()。該方法可以用來解決死鎖問題。
響應(yīng)中斷的例子:
public?class?ReentrantLockTest?{
????static?Lock?lock1?=?new?ReentrantLock();
????static?Lock?lock2?=?new?ReentrantLock();
????public?static?void?main(String[]?args)?throws?InterruptedException?{
????????Thread?thread?=?new?Thread(new?ThreadDemo(lock1,?lock2));//該線程先獲取鎖1,再獲取鎖2
????????Thread?thread1?=?new?Thread(new?ThreadDemo(lock2,?lock1));//該線程先獲取鎖2,再獲取鎖1
????????thread.start();
????????thread1.start();
????????thread.interrupt();//是第一個線程中斷
????}
????static?class?ThreadDemo?implements?Runnable?{
????????Lock?firstLock;
????????Lock?secondLock;
????????public?ThreadDemo(Lock?firstLock,?Lock?secondLock)?{
????????????this.firstLock?=?firstLock;
????????????this.secondLock?=?secondLock;
????????}
????????@Override
????????public?void?run()?{
????????????try?{
????????????????firstLock.lockInterruptibly();
????????????????TimeUnit.MILLISECONDS.sleep(10);//更好的觸發(fā)死鎖
????????????????secondLock.lockInterruptibly();
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}?finally?{
????????????????firstLock.unlock();
????????????????secondLock.unlock();
????????????????System.out.println(Thread.currentThread().getName()+"正常結(jié)束!");
????????????}
????????}
????}
}
結(jié)果:
構(gòu)造死鎖場景:
創(chuàng)建兩個子線程,子線程在運行時會分別嘗試獲取兩把鎖。其中一個線程先獲取鎖1在獲取鎖2,另一個線程正好相反。
如果沒有外界中斷,該程序?qū)⑻幱谒梨i狀態(tài)永遠無法停止。我們通過使其中一個線程中斷,來結(jié)束線程間毫無意義的等待。被中斷的線程將拋出異常,而另一個線程將能獲取鎖后正常結(jié)束。
3.3 獲取鎖時限時等待
ReentrantLock還給我們提供了獲取鎖限時等待的方法tryLock(),可以選擇傳入時間參數(shù),表示等待指定的時間,無參則表示立即返回鎖申請的結(jié)果:true表示獲取鎖成功,false表示獲取鎖失敗。我們可以使用該方法配合失敗重試機制來更好的解決死鎖問題。
更好的解決死鎖的例子:
public?class?ReentrantLockTest?{
????static?Lock?lock1?=?new?ReentrantLock();
????static?Lock?lock2?=?new?ReentrantLock();
????public?static?void?main(String[]?args)?throws?InterruptedException?{
????????Thread?thread?=?new?Thread(new?ThreadDemo(lock1,?lock2));//該線程先獲取鎖1,再獲取鎖2
????????Thread?thread1?=?new?Thread(new?ThreadDemo(lock2,?lock1));//該線程先獲取鎖2,再獲取鎖1
????????thread.start();
????????thread1.start();
????}
????static?class?ThreadDemo?implements?Runnable?{
????????Lock?firstLock;
????????Lock?secondLock;
????????public?ThreadDemo(Lock?firstLock,?Lock?secondLock)?{
????????????this.firstLock?=?firstLock;
????????????this.secondLock?=?secondLock;
????????}
????????@Override
????????public?void?run()?{
????????????try?{
????????????????while(!lock1.tryLock()){
????????????????????TimeUnit.MILLISECONDS.sleep(10);
????????????????}
????????????????while(!lock2.tryLock()){
????????????????????lock1.unlock();
????????????????????TimeUnit.MILLISECONDS.sleep(10);
????????????????}
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}?finally?{
????????????????firstLock.unlock();
????????????????secondLock.unlock();
????????????????System.out.println(Thread.currentThread().getName()+"正常結(jié)束!");
????????????}
????????}
????}
}
結(jié)果:
線程通過調(diào)用
tryLock()方法獲取鎖,第一次獲取鎖失敗時會休眠10毫秒,然后重新獲取,直到獲取成功。第二次獲取失敗時,首先會釋放第一把鎖,再休眠10毫秒,然后重試直到成功為止。線程獲取第二把鎖失敗時將會釋放第一把鎖,這是解決死鎖問題的關(guān)鍵,避免了兩個線程分別持有一把鎖然后相互請求另一把鎖。
結(jié)合Condition實現(xiàn)等待通知機制
使用synchronized結(jié)合Object上的wait和notify方法可以實現(xiàn)線程間的等待通知機制。ReentrantLock結(jié)合Condition接口同樣可以實現(xiàn)這個功能。而且相比前者使用起來更清晰也更簡單。
Condition使用簡介
Condition由ReentrantLock對象創(chuàng)建,并且可以同時創(chuàng)建多個
static?Condition?notEmpty?=?lock.newCondition();
static?Condition?notFull?=?lock.newCondition();
Condition接口在使用前必須先調(diào)用ReentrantLock的lock()方法獲得鎖。之后調(diào)用Condition接口的await()將釋放鎖,并且在該Condition上等待,直到有其他線程調(diào)用Condition的signal()方法喚醒線程。使用方式和wait,notify類似。
一個使用condition的簡單例子:
public?class?ConditionTest?{
????static?ReentrantLock?lock?=?new?ReentrantLock();
????static?Condition?condition?=?lock.newCondition();
????public?static?void?main(String[]?args)?throws?InterruptedException?{
????????lock.lock();
????????new?Thread(new?SignalThread()).start();
????????System.out.println("主線程等待通知");
????????try?{
????????????condition.await();
????????}?finally?{
????????????lock.unlock();
????????}
????????System.out.println("主線程恢復(fù)運行");
????}
????static?class?SignalThread?implements?Runnable?{
????????@Override
????????public?void?run()?{
????????????lock.lock();
????????????try?{
????????????????condition.signal();
????????????????System.out.println("子線程通知");
????????????}?finally?{
????????????????lock.unlock();
????????????}
????????}
????}
}
運行結(jié)果:
使用Condition實現(xiàn)簡單的阻塞隊列
阻塞隊列是一種特殊的先進先出隊列,它有以下幾個特點 1.入隊和出隊線程安全 2.當隊列滿時,入隊線程會被阻塞;當隊列為空時,出隊線程會被阻塞。
阻塞隊列的簡單實現(xiàn):
public?class?MyBlockingQueue<E>?{
????int?size;//阻塞隊列最大容量
????ReentrantLock?lock?=?new?ReentrantLock();
????LinkedList?list=new?LinkedList<>();//隊列底層實現(xiàn)
????Condition?notFull?=?lock.newCondition();//隊列滿時的等待條件
????Condition?notEmpty?=?lock.newCondition();//隊列空時的等待條件
????public?MyBlockingQueue(int?size)?{
????????this.size?=?size;
????}
????public?void?enqueue(E?e)?throws?InterruptedException?{
????????lock.lock();
????????try?{
????????????while?(list.size()?==size)//隊列已滿,在notFull條件上等待
????????????????notFull.await();
????????????list.add(e);//入隊:加入鏈表末尾
????????????System.out.println("入隊:"?+e);
????????????notEmpty.signal();?//通知在notEmpty條件上等待的線程
????????}?finally?{
????????????lock.unlock();
????????}
????}
????public?E?dequeue()?throws?InterruptedException?{
????????E?e;
????????lock.lock();
????????try?{
????????????while?(list.size()?==?0)//隊列為空,在notEmpty條件上等待
????????????????notEmpty.await();
????????????e?=?list.removeFirst();//出隊:移除鏈表首元素
????????????System.out.println("出隊:"+e);
????????????notFull.signal();//通知在notFull條件上等待的線程
????????????return?e;
????????}?finally?{
????????????lock.unlock();
????????}
????}
}
測試代碼
public?static?void?main(String[]?args)?throws?InterruptedException?{
????MyBlockingQueue?queue?=?new?MyBlockingQueue<>(2);
????for?(int?i?=?0;?i?10;?i++)?{
????????int?data?=?i;
????????new?Thread(new?Runnable()?{
????????????@Override
????????????public?void?run()?{
????????????????try?{
????????????????????queue.enqueue(data);
????????????????}?catch?(InterruptedException?e)?{
????????????????}
????????????}
????????}).start();
????}
????for(int?i=0;i<10;i++){
????????new?Thread(new?Runnable()?{
????????????@Override
????????????public?void?run()?{
????????????????try?{
????????????????????Integer?data?=?queue.dequeue();
????????????????}?catch?(InterruptedException?e)?{
????????????????????e.printStackTrace();
????????????????}
????????????}
????????}).start();
????}
}
運行結(jié)果:
總結(jié)
ReentrantLock是可重入的獨占鎖。比起synchronized功能更加豐富,支持公平鎖實現(xiàn),支持中斷響應(yīng)以及限時等待等等??梢耘浜弦粋€或多個Condition條件方便的實現(xiàn)等待通知機制。
好了,今天就分享這么多。
參考:www.cnblogs.com/takumicx
