Java多線程進階(二)—— J.U.C之locks框架:接口

timg (3).jpeg

本文首發於一世流雲的專欄: https://segmentfault.com/blog...

本系列文章中所說的juc-locks鎖框架就是指java.util.concurrent.locks包,該包提供了一系列基礎的鎖工具,用以對synchronizd、wait、notify等進行補充、加強。
juc-locks鎖框架中一共就三個接口:Lock、Condition、ReadWriteLock,接下來對這些接口做介紹,更詳細的信息能夠參考Oracle官方的文檔java

1、Lock接口簡介

Lock接口能夠視爲synchronized的加強版,提供了更靈活的功能。該接口提供了限時鎖等待、鎖中斷、鎖嘗試等功能。segmentfault

1.1 接口定義

該接口的方法聲明以下:
Locks接口api

須要注意lock()lockInterruptibly()這兩個方法的區別:oracle

lock()方法相似於使用synchronized關鍵字加鎖,若是鎖不可用,出於線程調度目的,將禁用當前線程,而且在得到鎖以前,該線程將一直處於休眠狀態。
lockInterruptibly()方法顧名思義,就是若是鎖不可用,那麼當前正在等待的線程是能夠被中斷的,這比synchronized關鍵字更加靈活。

1.2 使用示例

能夠看到,Lock做爲一種同步器,通常會用一個finally語句塊確保鎖最終會釋放。框架

Lock lock = ...;
if (lock.tryLock()) {
    try {
        // manipulate protected state
    } finally {
        lock.unlock();
    }
} else {
    // perform alternative actions
}

2、Condition接口簡介

Condition能夠看作是Obejct類的wait()notify()notifyAll()方法的替代品,與Lock配合使用。
當線程執行condition對象的await方法時,當前線程會當即釋放鎖,並進入對象的等待區,等待其它線程喚醒或中斷。工具

JUC在實現Conditon對象時,實際上是經過實現AQS框架,來實現了一個Condition等待隊列,這個在後面講AQS框架時會詳細介紹,目前只要瞭解Condition如何使用便可。

2.1 接口定義

Condition接口

2.2 使用示例

Oracle官方文檔中給出了一個緩衝隊列的示例:性能

假定有一個緩衝隊列,支持 put 和 take 方法。若是試圖在空隊列中執行 take 操做,則線程將一直阻塞,直到隊列中有可用元素;若是試圖在滿隊列上執行 put 操做,則線程也將一直阻塞,直到隊列不滿。測試

class BoundedBuffer {
    final Lock lock = new ReentrantLock();
    final Condition notFull = lock.newCondition();
    final Condition notEmpty = lock.newCondition();
 
    final Object[] items = new Object[100];
    int putptr, takeptr, count;
 
    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)    //防止虛假喚醒,Condition的await調用通常會放在一個循環判斷中
                notFull.await();
            items[putptr] = x;
            if (++putptr == items.length)
                putptr = 0;
            ++count;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }
 
    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                notEmpty.await();
            Object x = items[takeptr];
            if (++takeptr == items.length)
                takeptr = 0;
            --count;
            notFull.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }
}
等待 Condition 時,爲了防止發生「虛假喚醒」, Condition 通常都是在一個循環中被等待,並測試正被等待的狀態聲明,如上述代碼註釋部分。
雖然上面這個示例程序即便不用while,改用if判斷也不會出現問題,可是最佳實踐仍是作while循環判斷—— Guarded Suspension模式,以防遺漏狀況。

3、ReadWriteLock接口簡介

ReadWriteLock接口是一個單獨的接口(未繼承Lock接口),該接口提供了獲取讀鎖和寫鎖的方法。spa

所謂讀寫鎖,是一對相關的鎖——讀鎖和寫鎖,讀鎖用於只讀操做,寫鎖用於寫入操做。讀鎖能夠由多個線程同時保持,而寫鎖是獨佔的,只能由一個線程獲取。

3.1 接口定義

ReadWriteLock接口

3.2 使用注意

讀寫鎖的阻塞狀況以下圖:
讀寫鎖的阻塞線程

舉個例子,假設我有一份共享數據——訂單金額,大多數狀況下,線程只會進行高頻的數據訪問(讀取訂單金額),數據修改(修改訂單金額)的頻率較低。
那麼通常狀況下,若是採用互斥鎖,讀/寫和讀/讀都是互斥的,性能顯然不如採用讀寫鎖。

另外,因爲讀寫鎖自己的實現就遠比獨佔鎖複雜,所以,讀寫鎖比較適用於如下情形:

  1. 高頻次的讀操做,相對較低頻次的寫操做;
  2. 讀操做所用時間不會過短。(不然讀寫鎖自己的複雜實現所帶來的開銷會成爲主要消耗成本)
相關文章
相關標籤/搜索