1. 程式人生 > >多執行緒之重入鎖ReentrantLock(四)

多執行緒之重入鎖ReentrantLock(四)

在博文多執行緒之記憶體可見性Volatile(一)多執行緒之原子變數CAS演算法(二)中,我介紹瞭如何安全的訪問共享物件,給了兩種解決方案,java5.0之後,增加了lock介面的高階功能。

這篇博文,我們介紹lock介面的一種實現,重入鎖ReentrantLock,我們只是簡單的介紹和synchronized進行一下對比。對於重入鎖更加深入的內容,後面我們會再詳細的寫。

ReentrantLock

ReentrantLock是一個可重入的互斥鎖,重入鎖是一種遞迴無阻塞的同步機制。即ReentrantLock實現了Lock介面,並提供了與synchronized相同的互斥性和記憶體可見性。但相較於synchronized提供了更高的處理鎖的靈活性。

可重入概念

若一個程式或子程式可以“安全的被並行執行(Parallel computing)”,則稱其為可重入(reentrant或re-entrant)的。即當該子程式正在執行時,可以再次進入並執行它(並行執行時,個別的執行結果,都符合設計時的預期)。可重入概念是在單執行緒作業系統的時代提出的。

重入鎖的構造方法提供了一個可選的公平引數:

不公平鎖與公平鎖的區別:

  1. 公平情況下,操作會排一個隊按順序執行,來保證執行順序。(會消耗更多的時間來排隊)
  2. 不公平情況下,是無序狀態允許插隊,jvm會自動計算如何處理更快速來排程插隊。(如果不關心順序,這個速度會更快)
 public
ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }

ReentrantLock與synchronized的比較

相同:ReentrantLock提供了synchronized類似的功能和記憶體語義。
不同:
(1)ReentrantLock功能性方面更全面,比如時間鎖等候,可中斷鎖等候,鎖投票等,因此更有擴充套件性。在多個條件變數和高度競爭鎖的地方,用ReentrantLock更合適,ReentrantLock還提供了Condition,對執行緒的等待和喚醒等操作更加靈活,一個ReentrantLock可以有多個Condition例項,所以更有擴充套件性。
(2)ReentrantLock 的效能比synchronized會好點。
(3)ReentrantLock提供了可輪詢的鎖請求,他可以嘗試的去取得鎖,如果取得成功則繼續處理,取得不成功,可以等下次執行的時候處理,所以不容易產生死鎖,而synchronized則一旦進入鎖請求要麼成功,要麼一直阻塞,所以更容易產生死鎖。

實現多個客戶端同時售票功能

我們使用三個執行緒,同時售賣20張車票,程式碼如下:

public class TestLock {

    public static void main(String[] args) {

        Ticket ticket = new Ticket();
        new Thread(ticket,"1號視窗").start();
        new Thread(ticket,"2號視窗").start();
        new Thread(ticket,"3號視窗").start();
    }
}

class Ticket implements Runnable{

    private int tick = 20;

    private Lock lock = new ReentrantLock();

    @Override
    public void run() {

        while(true){
            lock.lock();//如果被其它資源鎖定,會在此等待鎖釋放,達到暫停的效果  
            if(tick > 0){
                try {
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                    }
                    System.out.println(Thread.currentThread().getName() + "完成售票,餘票為:" + --tick);
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

注意:
(1) lock 必須在 finally 塊中釋放。否則,如果受保護的程式碼將丟擲異常,鎖就有可能永遠得不到釋放!這一點區別看起來可能沒什麼,但是實際上,它極為重要。忘記在 finally 塊中釋放鎖,可能會在程式中留下一個定時炸彈,當有一天炸彈爆炸時,您要花費很大力氣才有找到源頭在哪。而使用同步,JVM 將確保鎖會獲得自動釋放

總結

重入鎖,還有很多的特性,還需要我們更加深入的學習,繼續加油吧。