在講重入鎖(ReentrantLock)之前,相信大家都synchronized很熟悉了,它也是同步控制的一個重要鎖,決定了一個線程是否可以通路臨界資源,同時synchronized配合Object.wait()和Object.notify()的配合使用起到了等待通知的作用。這裡如果大家不是很熟悉,可以查閱資料熟悉一下synchronized的使用。那麼有synchronized這個鎖,為什麼還要介紹ReentrantLock,這肯定是有原因,接下來我就會介紹ReentrantLock比synchronized鎖的優點。再說到優點之前,我們先看看ReentrantLock的基本使用。
ReenrantLock的基本使用
public class ReenterLock implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
public static int i =0;
@Override
public void run() {
for(int j = 0;j<10000;j++){
lock.lock();
try {
i++;
}
finally{
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLock t = new ReenterLock();
Thread t1 =new Thread(t);
Thread t2 =new Thread(t);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
通過上面代碼我們可以看到ReentrantLock的lock()和unlock()方法要配合try/finally語句塊來完成,這是ReentrantLock的文法風格,希望記住。ReentrantLock之是以叫重入鎖不是根據它的名字翻譯過來的哦,下面我們再看下面代碼:
lock.lock();
lock.lock();
try{
i++;
}finally{
lock.unlock();
lock.unlock();
}
在這種情況下,一個線程連續兩次獲得同一把鎖,是允許,如果不允許這麼操作,那麼同一個線程在第二次獲得鎖時,将會與自己産生死鎖。是以重入鎖之是以叫重入鎖,就是表示同一個線程可以多次獲得鎖,隻要在釋放鎖時候,與加鎖的次數必須相同。這裡強調一下synchronized也是可重入的。接下來就說說ReentrantLock相對synchronized的優點:
ReetrantLock的進階功能
- 可以中斷響應
對于synchronized來說,如果一個線程在等待鎖,那麼結果隻有兩種情況,要麼它獲得鎖繼續執行,要麼它就保持等待。而重入鎖提供了另外一種可能,那就是線程可以中斷。也就是在等待鎖的過程,程式可以根據需要取消對鎖的等待,看如下代碼:
public class ReenterLock implements Runnable{
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public ReenterLock(int lock){
this.lock = lock;
}
@Override
public void run() {
try{
if (lock == 1){
lock1.lockInterruptibly();
try{
Thread.sleep(500);
}catch(InterruptedException e){}
lock2.lockInterruptibly();}
else{
lock2.lockInterruptibly();
try{
Thread.sleep(500);
}catch(InterruptedException e){}
lock1.lockInterruptibly();
}
}catch(InterruptedException e){
e.printStackTrace();
}finally{
if(lock1.isHeldByCurrentThread())
lock1.unlock();
if(lock2.isHeldByCurrentThread())
lock2.unlock();
System.out.println(Thread.currentThread().getId()+":線程退出");
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLock r1 = new ReenterLock(1);
ReenterLock r2 = new ReenterLock(2);
Thread t1 =new Thread(r1);
Thread t2 =new Thread(r2);
t1.start();t2.start();
Thread.sleep(1000);
t2.interrupt();
}
}
再看看結果:
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:896)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340)
at demo.ReenterLock.run(ReenterLock.java:29)
at java.lang.Thread.run(Thread.java:745)
10:線程退出
9:線程退出
閱讀上面代碼和結果我們就會發現線程t1和t2啟動後,t1先占用lock1,再占用lock2;t2先占用lock2,再請求lock1。是以很容易形成t1和t2之間互相等待,變成死鎖。但是這裡我們使用了t2.interrupt()也就是上面代碼最後一條語句,由于我們使用的lockInterruptibly(),這個方法是可以對中斷進行響應的。是以t2就會放棄lock1的申請,同時釋放已獲得lock2。最後t1可以得到lock2繼續執行下去。
- 鎖等待限時
除了等待外部通知之外,要避免死鎖還有一種方法,那就是限時等待。舉個例子,你等待朋友一起打球,在等待1小時,如果朋友遲遲不來,又無法聯系到他,那麼我們應該掃興離去。對線程來說也是這樣的,我們給一個線程等待鎖的時間,如果在這個時間等不到鎖,那麼線程就會放棄。
public class ReenterLock implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try{
if(lock.tryLock(5, TimeUnit.SECONDS))
Thread.sleep(6000);
else{
System.out.println("get this lock fialed");
}
}catch(InterruptedException e){
e.printStackTrace();
}finally{
if(lock.isHeldByCurrentThread())
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLock t = new ReenterLock();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();t2.start();
}
}
在這裡,tryLock()方法接受兩個參數,一個表示等待時長,另外一個表示計時機關。這裡的機關為秒,時長為5,表示線程在這個鎖請求中,最多等待5秒。如果超過5秒還沒有得到鎖,就會傳回false。這裡再介紹一下,ReentrantLock.tryLock()方法也可以不帶參數直接運作。這種情況下,目前線程會嘗試獲得鎖,如果所并未被其他線程占用個,則申請所成功,傳回true。否則申請失敗,立即傳回false。
- 公平鎖
在大多數情況下,鎖的申請都是非公平的。也就是說,線程1首先請求了鎖A,接着線程2頁請求了鎖A。那麼當鎖A可用時,是線程1可以獲得鎖還是線程2可以獲得鎖?在synchronized這個是不一定,系統會從這個鎖的等待隊列中随機挑選一個。不管誰先誰後,其實這就是不公平的。而公平鎖,則不是這樣,它會按照時間的先後順序,保證先到者先得。這樣就不會産生饑餓現象。雖然看起來公平鎖很好,但是公平鎖需要維護一個有序隊列,是以性能就會下降,是以預設情況下,ReentrantLock是非公平的。下面看一段代碼體驗一下公平鎖:
public class ReenterLock implements Runnable{
public static ReentrantLock fairlock = new ReentrantLock(true);//設定為true開啟公平鎖,預設是false
@Override
public void run() {
while(true){
try{
fairlock.lock();
System.out.println(Thread.currentThread().getName()+"獲得鎖");
}finally{
fairlock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLock t = new ReenterLock();
Thread t1 = new Thread(t,"Thread_1");
Thread t2 = new Thread(t,"Thread_2");
t1.start();t2.start();
}
}
上面代碼在通過ReentranLock(boolean fair)設定為true開啟公平鎖,接下來看看結果:
Thread_1獲得鎖
Thread_2獲得鎖
Thread_1獲得鎖
Thread_2獲得鎖
Thread_1獲得鎖
Thread_2獲得鎖
Thread_1獲得鎖
Thread_2獲得鎖
Thread_1獲得鎖
由于代碼有大量的輸出,這裡隻截取部分進行說明。這個輸出結果中,很明顯可以看到,兩個線程是交替執行的。
Condition條件
如果大家知道Object.wait 和 Object.notify方法的話,那麼就能很容易了解Condition 了,它們的作用大緻相同的。Object.wait 和 Object.notify方法的使用必須配合sysnchronized關鍵字合作使用。這個因為無論是wait()還是notify()都需要首先獲得目标對象的一個螢幕,這個螢幕也就是synchronized綁定的那個對象,而wait()和notify()方法執行後,就會釋放這個螢幕,下次執行還是需要獲得這個螢幕。而Condition是與重入鎖相關聯的。也是基于這個原因。下面就來通過一段代碼來進行解釋:
public class ReenterLock implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
@Override
public void run() {
try{
lock.lock();
condition.await();
System.out.println("Thread is going on");
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLock t = new ReenterLock();
Thread t1 = new Thread(t);
t1.start();
Thread.sleep(2000);
//通知線程t1繼續執行
lock.lock();
condition.signal();
lock.unlock();
}
}
代碼第三行,通過lock生成一個與之綁定的Condition對象。代碼condition.await(),要求線程Condition對象上進行等待。代碼condition.signa()由主線程main發出通知,告知在Condition上的線程可以繼續執行了,這裡看到到和wait()和notify()一樣,當線程使用Condition.await()時,要求線程持有相關的重入鎖,在Condition.await()調用後,這個線程就會釋放。同理,在Conditon.signal()方法調用時,也要求線程先獲得鎖,在signal()方法調用之後,系統會從目前Condition等待對象中喚醒一個線程。