突擊併發編程JUC系列-ReentrantLock

突擊併發編程JUC系列演示代碼地址: https://github.com/mtcarpenter/JavaTutorial前端

鎖是用來控制多個線程訪問共享資源的方式,經過鎖能夠防止多個線程同時訪問共享資源。在 Java1.5以前實現鎖只能使用 synchronized關鍵字實現,可是synchronized隱式獲取釋放鎖,在 1.5以後官方新增了 lock 接口也是用來實現鎖的功能,,它具有與synchronized關鍵字相似的同步功能,顯式的獲取和釋放鎖。lock擁有了鎖獲取與釋放的可操做性、可中斷的獲取鎖以及超時獲取鎖等多種synchronized關鍵字所不具有的同步特性。java

LOCK 方法說明

  • void lock():獲取鎖,調用該方法當前線程將會獲取鎖,當鎖得到後,從該方法返回
  • void lockInterruptibly() throws InterruptedException:可中斷地獲取鎖,和 lock方法地不一樣之處在於該方法會響應中斷,即在鎖的獲取中能夠中斷當前線程
  • boolean tryLock(): 嘗試非阻塞地獲取鎖,調用該方法後馬上返回,若是可以獲取則返回 true 不然 返回false
  • boolean tryLock(long time, TimeUnit unit):超時地獲取鎖,當前線程在如下 3 種狀況下會返回:
    • 當前線程在超時時間內得到了鎖
    • 當前線程在超時時間被中斷
    • 超時時間結束後,返回 false
  • void unlock(): 釋放鎖
  • Condition newCondition():獲取鎖等待通知組件,該組件和當前的鎖綁定,當前線程只有得到了鎖,才能調用該組件的 wait() 方法,而調用後,當前線程將釋放鎖。

ReentrantLock 簡介

Lock 做爲接口類爲咱們提供一組方法,只能經過的實現類進行 Lock 方法,今天咱們就講講繼承Lock接口一個可重入的獨佔鎖 ReentrantLock 實現類,ReentrantLock經過自定義隊列同步器(Abstract Queued Sychronized,AQS)來實現鎖的獲取與釋放。它使用了一個 int 成員變量表示同步狀態,經過內置的 FIFO 隊列來完成資源獲取線程的排隊工做,併發包的做者(Doug Lea)指望它可以成爲實現大部分同步需求的基礎。git

獨佔鎖指該鎖在同一時刻只能被一個線程獲取,而獲取鎖的其餘線程只能在同步隊列中等待;可重入鎖指該鎖可以支持一個線程對同一個資源執行屢次加鎖操做。ReentrantLock支持公平鎖和非公平鎖的實現。公平指線程競爭鎖的機制是公平的,而非公平指不一樣的線程獲取鎖的機制是不公平的。ReentrantLock不但提供了synchronized對鎖的操做功能,還提供了諸如可響應中斷鎖、可輪詢鎖請求、定時鎖等避免多線程死鎖的方法。github

ReentrantLock 方法說明

  • ReentrantLock() : 無參 ReentrantLock 使用的非公平鎖。
  • ReentrantLock(boolean fair):ReentrantLock 能夠初始化設置是公平鎖鎖,仍是非公平鎖。
  • getHoldCount():查詢當前線程在某個 Lock上的數量,若是當前線程成功獲取了 Lock,那麼該值大於等於 1;若是沒有獲取到 Lock 的線程調用該方法,則返回值爲 0 。
  • isHeldByCurrentThread():判斷當前線程是否持有某個 Lock,因爲 Lock 的排他性,所以在某個時刻只有一個線程調用該方法返回 true。
  • isLocked():判斷Lock是否已經被線程持有。
  • isFair():建立的ReentrantLock是否爲公平鎖。
  • hasQueuedThreads():在多個線程試圖獲取Lock的時候,只有一個線程可以正常得到,其餘線程可能(若是使用 tryLock()方法失敗則不會進入阻塞)會進入阻塞,該方法的做用就是查詢是否有線程正在等待獲取鎖。
  • hasQueuedThread(Thread thread):在等待獲取鎖的線程中是否包含某個指定的線程。
  • getQueueLength():返回當前有多少個線程正在等待獲取鎖。

僞代碼回顧

精彩片斷 1 :編程

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

 
    public void m() {
        // 加鎖
      lock.lock();  
      try {
        // 業務執行
      } finally {
         // 釋放鎖
        lock.unlock()
      }
    }

}

精彩片斷 2 :後端

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

    public void m() {
        //嘗試獲取鎖
        if (lock.tryLock()) {
            try {
                //處理任務 .......
            } catch (Exception ex) {

            } finally {
                //釋放鎖
                lock.unlock();  
            }
        } else {
            //else 表示沒有獲取鎖 無需關閉
            // ..... 根據實際業務處理 (返回、處理其它邏輯)
        }
    }

}

lock.tryLock() : 阻塞式獲取鎖,若是可以獲取則返回 true 不然 返回 false。沒法獲取也能夠根據實際業務進行處理。多線程

案例上手

synchronized 案例

public class LockExample1 {

    // 請求總數
    public static int requestTotal = 10000;

    // 併發計數
    public static int count = 0;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
        for (int i = 0; i < requestTotal; i++) {

            executorService.execute(() -> {
                try {
                    add();
                } catch (Exception e) {
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("count = " + count);
    }

    private static synchronized void add() {
        count++;
    }
}
// 運行結果:count = 10000

add() 方法加上了 synchronized 鎖,保證了該方法在併發下也是同步的。併發

lock() 方法的使用

public class LockExample2 {

    // 請求總數
    public static int requestTotal = 10000;

    // 併發計數
    public static int count = 0;

    private final static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
        for (int i = 0; i < requestTotal; i++) {

            executorService.execute(() -> {
                try {
                    add();
                } catch (Exception e) {
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("count = " + count);
    }

    
    private static  void add() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}
// 運行結果:count = 10000

將須要同步的代碼放在 lock 和 unlock 之間,使用 lock 必定要記得釋放鎖。函數

tryLock() 方法

private static void add() {
        if (lock.tryLock()) {
            try {
                count++;
            } finally {
                //當獲取鎖成功時最後必定要記住finally去關閉鎖
                lock.unlock();   //釋放鎖
            }
        } else {
            //else時爲未獲取鎖,則無需去關閉鎖
            //若是不能獲取鎖,則直接作其餘事情
            System.out.println(Thread.currentThread().getName() + "沒有獲取鎖");
        }
    }

經過 tryLock() 方法就發如今併發的狀況下會有部分線程沒法獲取到鎖。線程

tryLock(long timeout, TimeUnit unit) 能夠設置超時時間

private static void add() throws InterruptedException {
        if (lock.tryLock(1000, TimeUnit.MILLISECONDS)) {
            try {
                count++;
            } finally {
                //當獲取鎖成功時最後必定要記住finally去關閉鎖
                lock.unlock();   //釋放鎖
            }
        } else {
            //else時爲未獲取鎖,則無需去關閉鎖
            //若是不能獲取鎖,則直接作其餘事情
            System.out.println(Thread.currentThread().getName() + "沒有獲取鎖");
        }
    }

ReentrantLock 提供了公平和非公平鎖的實現

  • 公平鎖: ReentrantLock lock = new ReentrantLock(true) 。是指多個線程在等待同一個鎖時,必須按照申請鎖的時間順序來依次得到鎖。
  • 非公平鎖: ReentrantLock lock = new ReentrantLock(false) 。若是構造函數不傳遞參數,則默認是非公平鎖。

歡迎關注公衆號 山間木匠 , 我是小春哥,從事 Java 後端開發,會一點前端、經過持續輸出系列技術文章以文會友,若是本文能爲您提供幫助,歡迎你們關注、 點贊、分享支持,咱們下期再見!<br />

相關文章
相關標籤/搜索