併發編程之 Java 三把鎖

三把鎖

前言

今天咱們繼續學習併發。在以前咱們學習了 JMM 的知識,知道了在併發編程中,爲了保證線程的安全性,須要保證線程的原子性,可見性,有序性。其中,synchronized 高頻出現,由於他既保證了原子性,也保證了可見性和有序性。爲何,由於 synchronized 是鎖。經過鎖,可讓本來並行的任務變成串行。然而如你所見,這也致使了嚴重的性能受損。所以,不到萬不得已,不要使用鎖,特別是吞吐量要求特別高的 WEB 服務器。若是鎖住,性能將呈幾何級降低。java

但咱們仍然須要鎖,在某些操做共享變量的時刻,仍然須要鎖來保證數據的準確性。而Java 世界有 3 把鎖,今天咱們主要說說這 3 把鎖的用法。程序員

  1. synchronized 關鍵字
  2. ReentrantLock 重入鎖
  3. ReadWriteLock 讀寫鎖

1. synchronized 關鍵字

synchronized 能夠說是咱們學習併發的時候第一個學習的關鍵字,該關鍵字粗魯有效,一般是初級程序員最愛使用的,也所以會常常致使一些性能損失和死鎖問題。編程

下面是 synchronized 的 3 個用法:安全

void resource1() {
    synchronized ("resource1") {
      System.out.println("做用在同步塊中");
    }
  }

  synchronized void resource3() {
    System.out.println("做用在實例方法上");
  }

  static synchronized void resource2() {
      System.out.println("做用在靜態方法上");
  }

整理如下這個關鍵字的用法:服務器

  1. 指定加鎖對象(代碼塊):對給定對象加鎖,進入同步代碼前要得到給定對象的鎖。
  2. 直接做用於實例方法:至關於對當前實例加鎖,進入同步代碼前要得到當前實例的鎖。
  3. 直接做用於靜態方法:至關於對當前類加鎖,進入同步代碼塊前要得到當前類的鎖。

synchronized 在發生異常的時候會釋放鎖,這點須要注意一下。多線程

synchronized 修飾的代碼在生產字節碼的時候會有 monitorenter 和 monitorexit 指令,而這兩個指令在底層調用了虛擬機8大指令中其中兩個指令-----lock 和 unlock。併發

synchronized 雖然萬能,可是仍是有不少侷限性,好比使用它常常會發生死鎖,且沒法處理,因此 Java 在 1.5版本的時候,加入了另外一個鎖 Lock 接口。咱們看看該接口下的有什麼。dom

2. ReentrantLock 重入鎖

JDK 在 1.5 版本新增了java.util.concurrent 包,有併發大師 Doug Lea 編寫,其中代碼鬼斧神工。值得咱們好好學習,包括今天說的 Lock。ide

Lock 接口函數

/**
 * @since 1.5
 * @author Doug Lea
 */
public interface Lock {

    void lock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();

    Condition newCondition();

void lock(); 得到鎖

void lockInterruptibly() ;

boolean tryLock(); 嘗試獲取鎖,若是獲取不到,馬上返回false。

boolean tryLock(long time, TimeUnit unit) 在

void unlock(); 在給定的時間裏等待鎖,超過期間則自動放棄

Condition newCondition(); 獲取一個重入鎖的好搭檔,搭配重入鎖使用

上面說了Lock的機構抽象方法,那麼 Lock 的實現是什麼呢?標準實現了 ReentrantLock, ReadWriteLock。也就是咱們今天講的重入鎖和讀寫鎖。咱們先講重入鎖。

先來一個簡單的例子:

package cn.think.in.java.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockText implements Runnable {

  /**
   * Re - entrant - Lock
   * 重入鎖,表示在單個線程內,這個鎖能夠反覆進入,也就是說,一個線程能夠連續兩次得到同一把鎖。
   * 若是你不容許重入,將致使死鎖。注意,lock 和 unlock 次數必定要相同,若是不一樣,就會致使死鎖和監視器異常。
   *
   * synchronized 只有2種狀況:1繼續執行,2保持等待。
   */
  static Lock lock = new ReentrantLock();
  static int i;

  public static void main(String[] args) throws InterruptedException {
    LockText lockText = new LockText();
    Thread t1 = new Thread(lockText);
    Thread t2 = new Thread(lockText);
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(i);
  }

  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      lock.lock();
      try {
        i++;
      } finally {
        // 由於lock 若是發生了異常,是不會釋放鎖的,因此必須在 finally 塊中釋放鎖
        // synchronized 發生異常會主動釋放鎖
        lock.unlock();
      }
    }
  }
}

在上面的代碼中,咱們使用了try 塊中保護了臨界資源 i 的操做。能夠看到, 重入鎖無論是開啓鎖仍是釋放鎖都是顯示的,其中須要注意的一點是,重入鎖運行時若是發生了異常,不會像 synchronized 釋放鎖,所以須要在 finally 中釋放鎖。不然將產生死鎖。

什麼是重入鎖?鎖就是鎖唄,爲何叫重入鎖?之因此這麼叫,那是由於這種鎖是能夠反覆進入的(一個線程),你們看看下面的代碼:

lock.lock();
lock.lock();
tyr{
  i++;
} finally{
  lock.unlock();
  lock.unlock();
}

在這種狀況下,一個線程連續兩次得到兩把鎖,這是容許的。若是不容許這麼操做,那麼同一個線程咋i第二次得到鎖是,將會和本身產生死鎖。固然,須要注意的是,若是你屢次得到了鎖,那麼也要相同的釋放屢次,若是釋放鎖的次數多了,就會獲得一個 IllegalMonitorStateException 異常,反之,若是釋放鎖的次數少了,那麼至關於這個線程尚未釋放鎖,其餘線程也就沒法進入臨界區。

重入鎖可以實現 synchronized 的全部功能,並且功能更爲強大,咱們看看有哪些功能。

中斷響應

對於 synchronized 來講,若是一個線程在等待鎖,那麼結果只有2種,要麼他得到這把鎖繼續運行,要麼他就保持等待。沒有第三種可能,那若是我有一個需求:須要線程在等待的時候中斷線程,synchronizded 是作不到的。而重入鎖能夠作到,就是 lockInterruptibly 方法,該方法能夠獲取鎖,而且在獲取鎖的過程種支持線程中斷,也就是說,若是調用了線程中斷方法,那麼就會拋出異常。相對於 lock 方法,是否是更爲強大?仍是寫個例子吧:

package cn.think.in.java.lock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLock(重入鎖)
 *
 * Condition(條件)
 *
 * ReadWriteLock(讀寫鎖)
 */
public class IntLock implements Runnable {

  /**
   * 默認是不公平的鎖,設置爲 true 爲公平鎖
   *
   * 公平:在多個線程的爭用下,這些鎖傾向於將訪問權授予等待時間最長的線程;
   * 使用公平鎖的程序在許多線程訪問時表現爲很低的整體吞吐量(即速度很慢,經常極其慢)
   * 還要注意的是,未定時的 tryLock 方法並無使用公平設置
   *
   * 不公平:此鎖將沒法保證任何特定訪問順序
   *
   * 拾遺:1 該類的序列化與內置鎖的行爲方式相同:一個反序列化的鎖處於解除鎖定狀態,無論它被序列化時的狀態是怎樣的。
   *      2.此鎖最多支持同一個線程發起的 2147483648 個遞歸鎖。試圖超過此限制會致使由鎖方法拋出的 Error。
   */
  static ReentrantLock lock1 = new ReentrantLock(true);
  static ReentrantLock lock2 = new ReentrantLock();
  int lock;

  /**
   * 控制加鎖順序,方便製造死鎖
   * @param lock
   */
  public IntLock(int lock) {
    this.lock = lock;
  }

  /**
   * lockInterruptibly 方法: 得到鎖,但優先響應中斷
   * tryLock 嘗試得到鎖,不等待
   * tryLock(long time , TimeUnit unit) 嘗試得到鎖,等待給定的時間
   */
  @Override
  public void run() {
    try {
      if (lock == 1) {
        // 若是當前線程未被中斷,則獲取鎖。
        lock1.lockInterruptibly();// 即在等待鎖的過程當中,能夠響應中斷。
        try {
          Thread.sleep(500);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        // 試圖獲取 lock 2 的鎖
        lock2.lockInterruptibly();
      } else {

        lock2.lockInterruptibly();
        try {
          Thread.sleep(500);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        // 該線程在企圖獲取 lock1 的時候,會死鎖,但被調用了 thread.interrupt 方法,致使中斷。中斷會放棄鎖。
        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 {

    /**
     * 這部分代碼主要是針對 lockInterruptibly 方法,該方法在線程發生死鎖的時候能夠中斷線程。讓線程放棄鎖。
     * 而 synchronized 是沒有這個功能的, 他要麼得到鎖繼續執行,要麼繼續等待鎖。
     */

    IntLock r1 = new IntLock(1);
    IntLock r2 = new IntLock(2);
    Thread t1 = new Thread(r1);
    Thread t2 = new Thread(r2);
    t1.start();
    t2.start();
    Thread.sleep(1000);
    // 中斷其中一個線程(只有線程在等待鎖的過程當中纔有效)
    // 若是線程已經拿到了鎖,中斷是不起任何做用的。
    // 注意:這點 synchronized 是不能實現此功能的,synchronized 在等待過程當中沒法中斷
    t2.interrupt();
    // t2 線程中斷,拋出異常,並放開鎖。沒有完成任務
    // t1 順利完成任務。
  }
}

在上面的代碼種,咱們分別啓動兩個線程,製造了一個死鎖,若是是 synchronized 是沒法解除這個死鎖的,這個時候重入鎖的威力就出來了,咱們調用線程的 interrupt 方法,中斷線程,咱們說,這個方法在線程 sleep,join ,wait 的時候,都會致使異常,這裏也一羊,因爲咱們使用的 lock 的 lockInterruptibly 方法,該方法就像咱們剛說的那樣,在等待鎖的時候,若是線程被中斷了,就會出現異常,同時調用了 finally 種的 unlock 方法,注意,咱們在 finally 中用 isHeldByCurrentThread 判斷當前線程是否持有此鎖,這是一種預防措施,放置線程沒有持有此鎖,致使出現 monitorState 異常。

鎖申請

除了等待通知以外,避免死鎖還有另外一種方法,就是超時等待,若是超過這個時間,線程就放棄獲取這把鎖,這點 ,synchronized 也是不支持的。那麼,如何使用呢?

package cn.think.in.java.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class TimeLock implements Runnable {

  static ReentrantLock lock = new ReentrantLock(false);

  @Override
  public void run() {
    try {
      // 最多等待5秒,超過5秒返回false,若得到鎖,則返回true
      if (lock.tryLock(5, TimeUnit.SECONDS)) {
        // 鎖住 6 秒,讓下一個線程沒法獲取鎖
        System.out.println("鎖住 6 秒,讓下一個線程沒法獲取鎖");
        Thread.sleep(6000);
      } else {
        System.out.println("get lock failed");
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      if (lock.isHeldByCurrentThread()) {
        lock.unlock();
      }
    }
  }

  public static void main(String[] args) {
    TimeLock tl = new TimeLock();
    Thread t1 = new Thread(tl);
    Thread t2 = new Thread(tl);

    t1.start();
    t2.start();


  }
}

上面的代碼中,咱們設置鎖的等待時間是5秒,可是在同步塊中,咱們設置了6秒暫停,鎖外面的線程等待了5面發現仍是不能獲取鎖,就會放棄。走 else 邏輯,結束執行,注意,這裏,咱們在 finally 塊中依然作了判斷,若是不作判斷,就會出現 IllegalMonitorStateException 異常。

固然了,tryLock 方法也能夠不帶時間參數,若是獲取不到鎖,馬上返回false,不然返回 true。該方法也是應對死鎖的一個好辦法。咱們仍是寫個例子:

package cn.think.in.java.lock;

import java.util.concurrent.locks.ReentrantLock;

public class TryLock implements Runnable {

  static ReentrantLock lock1 = new ReentrantLock();
  static ReentrantLock lock2 = new ReentrantLock();
  int lock;

  public TryLock(int lock) {
    this.lock = lock;
  }

  @Override
  public void run() {
    // 線程1
    if (lock == 1) {
      while (true) {
        // 獲取1的鎖
        if (lock1.tryLock()) {
          try {
            // 嘗試獲取2的鎖
            if (lock2.tryLock()) {
              try {
                System.out.println(Thread.currentThread().getId() + " : My Job done");
                return;
              } finally {
                lock2.unlock();
              }
            }
          } finally {
            lock1.unlock();
          }
        }
      }
    } else {
      // 線程2
      while (true) {
        // 獲取2的鎖
        if (lock2.tryLock()) {
          try {
            // 嘗試獲取1的鎖
            if (lock1.tryLock()) {
              try {
                System.out.println(Thread.currentThread().getId() + ": My Job done");
                return;
              } finally {
                lock1.unlock();
              }
            }
          } finally {
            lock2.unlock();
          }
        }
      }
    }
  }

  /**
   * 這段代碼若是使用 synchronized 確定會引發死鎖,可是因爲使用 tryLock,他會不斷的嘗試, 當第一次失敗了,他會放棄,而後執行完畢,並釋放外層的鎖,這個時候就是
   * 另外一個線程搶鎖的好時機。
   * @param args
   */
  public static void main(String[] args) {
    TryLock r1 = new TryLock(1);
    TryLock r2 = new TryLock(2);
    Thread t1 = new Thread(r1);
    Thread t2 = new Thread(r2);
    t1.start();
    t2.start();
  }
}

這段代碼若是使用 synchronized 確定會引發死鎖,可是因爲使用 tryLock,他會不斷的嘗試, 當第一次失敗了,他會放棄,而後執行完畢,並釋放外層的鎖,這個時候就是另外一個線程搶鎖的好時機。

公平鎖和非公平鎖

大多數狀況下,爲了效率,鎖都是不公平的。系統在選擇鎖的時候都是隨機的,不會按照某種順序,好比時間順序,公平鎖的一大特色:他不會產生飢餓現象。只要你排隊 ,最終仍是能夠獲得資源的。若是咱們使用 synchronized ,獲得的鎖就是不公平的。所以,這也是重入鎖比 synchronized 強大的一個優點。咱們一樣寫個例子:

package cn.think.in.java.lock;

import java.util.concurrent.locks.ReentrantLock;

public class FairLock implements Runnable {

  // 公平鎖和非公平鎖的結果徹底不一樣
  /*
  * 10 得到鎖
    10 得到鎖
    10 得到鎖
    10 得到鎖
    10 得到鎖
    10 得到鎖
    10 得到鎖
    10 得到鎖
    10 得到鎖
    10 得到鎖
    9 得到鎖
    9 得到鎖
    9 得到鎖
    9 得到鎖
    9 得到鎖
    9 得到鎖
    9 得到鎖
    9 得到鎖
    9 得到鎖
    9 得到鎖
    ======================下面是公平鎖,上面是非公平鎖
    10 得到鎖
    9 得到鎖
    10 得到鎖
    9 得到鎖
    10 得到鎖
    9 得到鎖
    10 得到鎖
    9 得到鎖
    10 得到鎖
    9 得到鎖
    10 得到鎖
    9 得到鎖
    10 得到鎖
    9 得到鎖
    10 得到鎖
    9 得到鎖
    10 得到鎖
    9 得到鎖
    10 得到鎖
    9 得到鎖
    10 得到
  *
  * */
  static ReentrantLock unFairLock = new ReentrantLock(false);
  static ReentrantLock fairLock = new ReentrantLock(true);

  @Override
  public void run() {
    while (true) {
      try {
        fairLock.lock();
        System.out.println(Thread.currentThread().getId() + " 得到鎖");
      } finally {
        fairLock.unlock();
      }
    }
  }

  /**
   * 默認是不公平的鎖,設置爲 true 爲公平鎖
   *
   * 公平:在多個線程的爭用下,這些鎖傾向於將訪問權授予等待時間最長的線程;
   * 使用公平鎖的程序在許多線程訪問時表現爲很低的整體吞吐量(即速度很慢,經常極其慢)
   * 還要注意的是,未定時的 tryLock 方法並無使用公平設置
   *
   * 不公平:此鎖將沒法保證任何特定訪問順序,可是效率很高
   *
   */
  public static void main(String[] args) {
    FairLock fairLock = new FairLock();
    Thread t1 = new Thread(fairLock, "cxs - t1");
    Thread t2 = new Thread(fairLock, "cxs - t2");
    t1.start();
    t2.start();
  }
}

重入鎖的構造函數有一個 boolean 參數,ture 表示公平,false 表示不公平,默認是不公平的,公平鎖會下降性能。代碼中由運行結果,能夠看到,公平鎖的打印順序是徹底交替運行,而不公平鎖的順序徹底是隨機的。注意:若是沒有特殊需求,請不要使用公平鎖,會大大下降吞吐量。

到這裏,咱們總結一下重入鎖相比 synchronized 有哪些優點:

  1. 能夠在線程等待鎖的時候中斷線程,synchronized 是作不到的。
  2. 能夠嘗試獲取鎖,若是獲取不到就放棄,或者設置必定的時間,這也是 synchroized 作不到的。
  3. 能夠設置公平鎖,synchronized 默認是非公平鎖,沒法實現公平鎖。

固然,你們會說, synchronized 能夠經過 Object 的 wait 方法和 notify 方法實現線程之間的通訊,重入鎖能夠作到嗎?樓主告訴你們,固然能夠了! JDK 中的阻塞隊列就是用重入鎖加 他的搭檔 condition 實現的。

重入鎖的好搭檔-----Condition

還記的剛開始說 Lock 接口有一個newCondition 方法嗎,該方法就是獲取 Condition 的。該 Condition 綁定了該鎖。Condition 有哪些方法呢?咱們看看:

public interface Condition {

    void await() throws InterruptedException;

    boolean await(long time, TimeUnit unit) throws InterruptedException;

    long awaitNanos(long nanosTimeout) throws InterruptedException;

    boolean await(long time, TimeUnit unit) throws InterruptedException;

    void awaitUninterruptibly();

    boolean awaitUntil(Date deadline) throws InterruptedException;

    void signal();

    void signalAll();
}

看着是否是特別屬性,Condition 爲了避免和 Object 類的 wait 方法衝突,使用 await 方法,而 signal 方法對應的就是 notify 方法。signalAll 方法對應的就是 notifyAll 方法。其中還有一些時間限制的 await 方法,和 Object 的 wait 方法的做用相同。注意,其中有一個 awaitUninterruptibly 方法,該方法從名字能夠看出,並不會響應線程的中斷,而 Object 的 wait 方法是會響應的。而 awaitUntil 方法就是等待到一個給定的絕對時間。除非調用了 signal 或者中斷了。如何使用呢?來一段代碼吧:

package cn.think.in.java.lock.condition;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 重入鎖的好搭檔
 *
 * await 使當前線程等待,同時釋放當前鎖,當其餘線程中使用 signal 或者 signalAll 方法時,線程會從新得到鎖並繼續執行。
 *       或者當線程被中斷時,也能跳出等待,這和 Object.wait 方法很類似。
 * awaitUninterruptibly() 方法與 await 方法基本相同,可是它並不會在等待過程當中響應中斷。
 * singal() 該方法用於喚醒一個在等待中的線程,相對的 singalAll 方法會喚醒全部在等待的線程,這和 Object.notify 方法很相似。
 */
public class ConditionTest implements Runnable {

  static Lock lock = new ReentrantLock();

  static Condition condition = lock.newCondition();


  @Override
  public void run() {
    try {
      lock.lock();
      // 該線程會釋放 lock 的鎖,也就是說,一個線程想調用 condition 的方法,必須先獲取 lock 的鎖。
      // 不然就會像 object 的 wait 方法同樣,監視器異常
      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 {
    ConditionTest t = new ConditionTest();
    Thread t1 = new Thread(t);
    t1.start();
    Thread.sleep(1000);
    // 通知 t1 繼續執行
    // main 線程必須獲取 lock 的鎖,才能調用 condition 的方法。不然就是監視器異常,這點和 object 的 wait 方法是同樣的。
    lock.lock(); // IllegalMonitorStateException
    // 從 condition 的等待隊列中,喚醒一個線程。
    condition.signal();
    lock.unlock();
  }
}

能夠說,condition 的使用方式和 Object 類的 wait 方法的使用方式很類似,不管在哪個線程中調用 await 或者 signal 方法,都必須獲取對應的鎖,不然會出現 IllegalMonitorStateException 異常。

到這裏,咱們能夠說, Condition 的實現比 Object 的 wait 和 notify 仍是強一點,其中就包括了等待到指定的絕對時間,而且還有一個不受線程中斷影響的 awaitUninterruptibly 方法。所以,咱們說,只要容許,請使用重入鎖,儘可能不要使用無腦的 synchronized 。雖然在 JDK 1.6 後, synchronized 被優化了,但仍然建議使用 重入鎖。

3. ReadWriteLock 讀寫鎖

偉大的 Doug Lea 不只僅創造了 重入鎖,還創造了 讀寫鎖。什麼是讀寫鎖呢?咱們知道,線程不安全的緣由來自於多線程對數據的修改,若是你不修改數據,根本不須要鎖。咱們徹底能夠將讀寫分離,提升性能,在讀的時候不使用鎖,在寫的時候才加入鎖。這就是 ReadWriteLock 的設計原理。

那麼,如何使用呢?

package cn.think.in.java.lock;

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {

  static Lock lock = new ReentrantLock();
  static ReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

  static Lock readLock = reentrantReadWriteLock.readLock();
  static Lock writeLock = reentrantReadWriteLock.writeLock();

  int value;

  public Object handleRead(Lock lock) throws InterruptedException {
    try {
      lock.lock();
      // 模擬讀操做,讀操做的耗時越多,讀寫鎖的優點就越明顯
      Thread.sleep(1000);
      return value;
    } finally {
      lock.unlock();
    }
  }

  public void handleWrite(Lock lock, int index) throws InterruptedException {
    try {
      lock.lock();
      Thread.sleep(1000); // 模擬寫操做
      value = index;

    } finally {
      lock.unlock();
    }
  }

  public static void main(String[] args) {
    final ReadWriteLockDemo demo = new ReadWriteLockDemo();
    Runnable readRunnable = new Runnable() {
      @Override
      public void run() {
        try {
          demo.handleRead(readLock);
//          demo.handleRead(lock);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    };

    Runnable writeRunnable = new Runnable() {
      @Override
      public void run() {
        try {
          demo.handleWrite(writeLock, new Random().nextInt());
//          demo.handleWrite(lock, new Random().nextInt());
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    };

    /**
     * 使用讀寫鎖,這段程序只須要2秒左右
     * 使用普通的鎖,這段程序須要20秒左右。
     */

    for (int i = 0; i < 18; i++) {
      new Thread(readRunnable).start();
    }

    for (int i = 18; i < 20; i++) {
      new Thread(writeRunnable).start();
    }


  }

}

使用 ReentrantReadWriteLock 的 readLock()方法能夠返回讀鎖,writeLock 能夠返回寫鎖,咱們使用普通的的重入鎖和讀寫鎖進行測試,怎麼測試呢?

兩個循環:一個循環開啓18個線程去讀數據,一個循環開啓兩個線程去寫。若是使用普通的重入鎖,將耗時20秒,由於普通的重入鎖在讀的時候依然是串行的。而若是使用讀寫鎖,只須要2秒,也就是寫的時候是串行的。讀的時候是並行的,極大的提升了性能。

注意:只要涉及到寫都是串行的。好比讀寫操做,寫寫操做,都是串行的,只有讀讀操做是並行的。

讀寫鎖 ReadWriteLock 接口只有 2個方法:

Lock readLock(); 返回一個讀鎖 Lock writeLock(); 返回一個寫鎖

他的標準實現類是 ReentrantReadWriteLock 類,該類和普通重入鎖同樣,也能實現公平鎖,中斷響應,鎖申請等特性。由於他們返回的讀鎖或者寫鎖都實現了 Lock 接口。

總結

到這裏,咱們已經將 Java 世界的三把鎖的使用弄清楚了,從分析的過程當中咱們知道了,JDK 1.5 的重入鎖徹底能夠代替關鍵字 synchronized ,能實現不少 synchronized 沒有的功能。好比中斷響應,鎖申請,公平鎖等,而重入鎖的搭檔 Condition 也比 Object 的wait 和notify 強大,好比有設置絕對時間的等待,還有忽略線程中斷的 await 方法,這些都是 synchronized 沒法實現的。還有優化讀性能的 讀寫鎖,在讀的時候徹底是並行的,在某些場景下,好比讀不少,寫不多,性能將是幾何級別的提高。

因此,之後,能不用 synchronzed 就不要用,用的很差就會致使死鎖。

今天的Java 三把鎖就介紹到這裏。

good luck !!!!

相關文章
相關標籤/搜索