java多線程之重入鎖ReentrantLock

在講重入鎖(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等待對象中喚醒一個線程。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章