1. 程式人生 > >Java併發程式設計札記-(四)JUC鎖-02Lock與ReentrantLock

Java併發程式設計札記-(四)JUC鎖-02Lock與ReentrantLock

今天學習Lock與ReentrantLock。

Java中的鎖有兩種,synchronized與Lock。因為使用synchronized並不需要顯示地加鎖與解鎖,所以往往稱synchronized為隱式鎖,而使用Lock時則相反,所以一般稱Lock為顯示鎖。想了解synchronized更多請移步Java併發程式設計札記-(一)基礎-06synchronized詳解

synchronized修飾方法或語句塊,所有鎖的獲取和釋放都必須出現在一個塊結構中。當需要靈活地獲取或釋放鎖時,synchronized顯然是不符合要求的。Lock介面的實現允許鎖在不同的範圍內獲取和釋放,並支援以任何順序獲取和釋放多個鎖。

一句話,Lock實現比synchronized更靈活。但凡事有利就有弊,不使用塊結構鎖就失去了使用synchronized修飾方法或語句時會出現的鎖自動釋放功能,在大多數情況下,Lock實現需要手動釋放鎖。

除了更靈活之外,Lock還有以下優點:

  • Lock 實現提供了使用 synchronized 方法和語句所沒有的其他功能,包括提供了一個非塊結構的獲取鎖嘗試 (tryLock())、一個獲取可中斷鎖的嘗試 (lockInterruptibly()) 和一個獲取超時失效鎖的嘗試 (tryLock(long, TimeUnit))。
  • Lock 類還可以提供與隱式監視器鎖完全不同的行為和語義,如保證排序、非重入用法或死鎖檢測。如果某個實現提供了這樣特殊的語義,則該實現必須對這些語義加以記錄。

ReentrantLock是一個可重入的互斥鎖。顧名思義,“互斥鎖”表示在某一時間點只能被同一執行緒所擁有。“可重入”表示鎖可被某一執行緒多次獲取。

當鎖沒有被某一執行緒佔有時,呼叫lock()方法的執行緒將成功獲取鎖。可以使用isHeldByCurrentThread()和 getHoldCount()方法來判斷當前執行緒是否擁有該鎖。

ReentrantLock既可以是公平鎖又可以是非公平鎖。當此類的構造方法ReentrantLock(boolean fair) 接收true作為引數時,ReentrantLock就是公平鎖,執行緒依次排隊獲取公平鎖,即鎖將被等待最長時間的執行緒佔有。與預設情況(使用非公平鎖)相比,使用公平鎖的程式在多執行緒環境下效率比較低。而且公平鎖不能保證執行緒排程的公平性,tryLock方法可在鎖未被其他執行緒佔用的情況下獲得該鎖。

方法列表

//構造方法摘要
ReentrantLock() 
          //建立一個 ReentrantLock 的例項。
ReentrantLock(boolean fair) 
          //建立一個具有給定公平策略的 ReentrantLock。  
//方法摘要
int getHoldCount() 
          //查詢當前執行緒保持此鎖的次數。
protected  Thread   getOwner() 
          //返回目前擁有此鎖的執行緒,如果此鎖不被任何執行緒擁有,則返回 nullprotected  Collection<Thread>   getQueuedThreads() 
          //返回一個 collection,它包含可能正等待獲取此鎖的執行緒。
 int    getQueueLength() 
          //返回正等待獲取此鎖的執行緒估計數。
protected  Collection<Thread>   getWaitingThreads(Condition condition) 
          //返回一個 collection,它包含可能正在等待與此鎖相關給定條件的那些執行緒。
 int    getWaitQueueLength(Condition condition) 
          //返回等待與此鎖相關的給定條件的執行緒估計數。
 boolean    hasQueuedThread(Thread thread) 
          //查詢給定執行緒是否正在等待獲取此鎖。
 boolean    hasQueuedThreads() 
          //查詢是否有些執行緒正在等待獲取此鎖。
 boolean    hasWaiters(Condition condition) 
          //查詢是否有些執行緒正在等待與此鎖有關的給定條件。
 boolean    isFair() 
          //如果此鎖的公平設定為 true,則返回 trueboolean    isHeldByCurrentThread() 
          //查詢當前執行緒是否保持此鎖。
 boolean    isLocked() 
          //查詢此鎖是否由任意執行緒保持。
 void   lock() 
          //獲取鎖。
 void   lockInterruptibly() 
          //如果當前執行緒未被中斷,則獲取鎖。
 Condition  newCondition() 
          //返回用來與此 Lock 例項一起使用的 Condition 例項。
 String toString() 
          //返回標識此鎖及其鎖定狀態的字串。
 boolean    tryLock() 
          //僅在呼叫時鎖未被另一個執行緒保持的情況下,才獲取該鎖。
 boolean    tryLock(long timeout, TimeUnit unit) 
          //如果鎖在給定等待時間內沒有被另一個執行緒保持,且當前執行緒未被中斷,則獲取該鎖。
 void   unlock() 
          //試圖釋放此鎖。

使用最典型的程式碼如下

class X {
    private final ReentrantLock lock = new ReentrantLock();
    // ...

    public void m() { 
        lock.lock();  // block until condition holds
        try {
            // ... method body
        } finally {
            lock.unlock()
        }
    }
}

修改下Java併發程式設計札記-(一)基礎-06synchronized詳解中火車票訂票系統例項,使用ReentrantLock實現執行緒安全。
例6:火車票訂票系統-ReentrantLock實現執行緒安全版

import java.util.concurrent.locks.ReentrantLock;

public class SellTickets {

    public static void main(String[] args) {
        TicketsWindow tw1 = new TicketsWindow();
        Thread t1 = new Thread(tw1, "一號視窗");
        Thread t2 = new Thread(tw1, "二號視窗");
        t1.start();
        t2.start();
    }
}

class TicketsWindow implements Runnable {
    private int tickets = 1;
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "還剩餘票:" + tickets + "張");
                    --tickets;
                    System.out.println(Thread.currentThread().getName() + "賣出一張火車票,還剩" + tickets + "張");
                } else {
                    System.out.println(Thread.currentThread().getName() + "餘票不足,暫停出售!");
                    try {
                        Thread.sleep(1000 * 60);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

在某些方法的引數中出現了Condition,在後面我們會詳細介紹Condition。

總結

  • 與synchronized 相比ReentrantLock的使用更靈活。Lock介面的實現允許鎖在不同的範圍內獲取和釋放,並支援以任何順序獲取和釋放多個鎖。
  • ReentrantLock具有與使用 synchronized 相同的一些基本行為和語義,但功能更強大。包括提供了一個非塊結構的獲取鎖嘗試 (tryLock())、一個獲取可中斷鎖的嘗試 (lockInterruptibly()) 和一個獲取超時失效鎖的嘗試 (tryLock(long, TimeUnit))。
  • ReentrantLock具有synchronized所沒有的許多特性,比如時間鎖等候、可中斷鎖等候、無塊結構鎖、多個條件變數或者輪詢鎖。
  • ReentrantLock可伸縮性強,應當在高度爭用的情況下使用它。

本文就講到這裡,想了解Java併發程式設計更多內容請參考:

參考:

END.