首頁  >  文章  >  Java  >  詳解Java多執行緒程式設計中互斥鎖ReentrantLock類別的用法

詳解Java多執行緒程式設計中互斥鎖ReentrantLock類別的用法

高洛峰
高洛峰原創
2017-01-05 15:54:141375瀏覽

0.關於互斥鎖

所謂互斥鎖, 指的是一次最多只能有一個線程持有的鎖. 在jdk1.5之前, 我們通常使用synchronized機制控制多個線程對共享資源的訪問.而現在, Lock提供了比synchronized機制更廣泛的鎖定操作, Lock和synchronized機制的主要區別:
synchronized機制提供了對與每個對象相關的隱式監視器鎖的訪問, 並強制所有鎖獲取和釋放均要出現在一個塊結構中, 當獲取了多個鎖時, 它們必須以相反的順序釋放. synchronized機制對鎖的釋放是隱式的, 只要線程運行的代碼超出了synchronized語句塊範圍, 鎖就會被釋放. 而Lock機制必須顯式的調用Lock對象的unlock()方法才能釋放鎖, 這為獲取鎖和釋放鎖不出現在同一個塊結構中, 以及以更自由的順序釋放鎖提供了可能。

1. ReentrantLock介紹
ReentrantLock是一個可重入的互斥鎖,又被稱為「獨佔鎖」。
顧名思義,ReentrantLock鎖在同一個時間點只能被一個執行緒鎖持有;而可重入的意思是,ReentrantLock鎖,可以被單一執行緒多次取得。
ReentrantLock分為「公平鎖」和「非公平鎖」。它們的差異體現在取得鎖的機制上是否公平。 「鎖」是為了保護競爭資源,防止多個線程同時操作線程而出錯,ReentrantLock在同一個時間點只能被一個線程獲取(當某線程獲取到“鎖”時,其它線程就必須等待);ReentraantLock是透過一個FIFO的等待佇列來管理取得該鎖所有執行緒的。在「公平鎖」的機制下,執行緒依序排隊取得鎖;而「非公平鎖」在鎖是可獲取狀態時,不管自己是不是在佇列的開頭都會取得鎖。

ReentrantLock函數列表

// 创建一个 ReentrantLock ,默认是“非公平锁”。
ReentrantLock()
// 创建策略是fair的 ReentrantLock。fair为true表示是公平锁,fair为false表示是非公平锁。
ReentrantLock(boolean fair)
 
// 查询当前线程保持此锁的次数。
int getHoldCount()
// 返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null。
protected Thread getOwner()
// 返回一个 collection,它包含可能正等待获取此锁的线程。
protected Collection<Thread> getQueuedThreads()
// 返回正等待获取此锁的线程估计数。
int getQueueLength()
// 返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程。
protected Collection<Thread> getWaitingThreads(Condition condition)
// 返回等待与此锁相关的给定条件的线程估计数。
int getWaitQueueLength(Condition condition)
// 查询给定线程是否正在等待获取此锁。
boolean hasQueuedThread(Thread thread)
// 查询是否有些线程正在等待获取此锁。
boolean hasQueuedThreads()
// 查询是否有些线程正在等待与此锁有关的给定条件。
boolean hasWaiters(Condition condition)
// 如果是“公平锁”返回true,否则返回false。
boolean isFair()
// 查询当前线程是否保持此锁。
boolean isHeldByCurrentThread()
// 查询此锁是否由任意线程保持。
boolean isLocked()
// 获取锁。
void lock()
// 如果当前线程未被中断,则获取锁。
void lockInterruptibly()
// 返回用来与此 Lock 实例一起使用的 Condition 实例。
Condition newCondition()
// 仅在调用时锁未被另一个线程保持的情况下,才获取该锁。
boolean tryLock()
// 如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
boolean tryLock(long timeout, TimeUnit unit)
// 试图释放此锁。
void unlock()

2. ReentrantLock範例
透過比較「範例1」和「範例2」,我們能夠清楚的認識lock和unlock的作用
2.1 範例1

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
// LockTest1.java
// 仓库
class Depot { 
 private int size;  // 仓库的实际数量
 private Lock lock;  // 独占锁
 
 public Depot() {
  this.size = 0;
  this.lock = new ReentrantLock();
 }
 
 public void produce(int val) {
  lock.lock();
  try {
   size += val;
   System.out.printf("%s produce(%d) --> size=%d\n", 
     Thread.currentThread().getName(), val, size);
  } finally {
   lock.unlock();
  }
 }
 
 public void consume(int val) {
  lock.lock();
  try {
   size -= val;
   System.out.printf("%s consume(%d) <-- size=%d\n", 
     Thread.currentThread().getName(), val, size);
  } finally {
   lock.unlock();
  }
 }
}; 
 
// 生产者
class Producer {
 private Depot depot;
 
 public Producer(Depot depot) {
  this.depot = depot;
 }
 
 // 消费产品:新建一个线程向仓库中生产产品。
 public void produce(final int val) {
  new Thread() {
   public void run() {
    depot.produce(val);
   }
  }.start();
 }
}
 
// 消费者
class Customer {
 private Depot depot;
 
 public Customer(Depot depot) {
  this.depot = depot;
 }
 
 // 消费产品:新建一个线程从仓库中消费产品。
 public void consume(final int val) {
  new Thread() {
   public void run() {
    depot.consume(val);
   }
  }.start();
 }
}
 
public class LockTest1 { 
 public static void main(String[] args) { 
  Depot mDepot = new Depot();
  Producer mPro = new Producer(mDepot);
  Customer mCus = new Customer(mDepot);
 
  mPro.produce(60);
  mPro.produce(120);
  mCus.consume(90);
  mCus.consume(150);
  mPro.produce(110);
 }
}

運行結果: :

(1) Depot 是個倉庫。透過produce()能往倉庫中生產貨物,透過consume()能消費倉庫中的貨物。透過獨佔鎖lock實現對倉庫的互斥存取:在操作(生產/消費)倉庫中貨品前,會先透過lock()鎖住倉庫,操作完之後再透過unlock()解鎖。

(2) Producer是生產者類別。呼叫Producer中的produce()函數可以新建一個執行緒往倉庫中生產產品。
(3) Customer是消費者類。呼叫Customer中的consume()函數可以新建一個執行緒消費倉庫中的產品。
(4) 在主執行緒main中,我們會新建1個生產者mPro,同時新建1個消費者mCus。它們分別向倉庫中生產/消費產品。
根據main中的生產/消費數量,倉庫最終剩餘的產品應該是50。運行結果是符合我們預期的!
這個模型有兩個問題:
(1) 現實中,倉庫的容量不可能是負數。但是,此模型中的倉庫容量可以為負數,這與現實相矛盾!
(2) 現實中,倉庫的容量是有限制的。但是,此模型中的容量確實沒有限制的!
這兩個問題,我們稍微會講到如何解決。現在,先看個簡單的範例2;透過比較「範例1」和「範例2」,我們能更清晰的認識lock(),unlock()的用途。

2.2 範例2

Thread-0 produce(60) --> size=60
Thread-1 produce(120) --> size=180
Thread-3 consume(150) <-- size=30
Thread-2 consume(90) <-- size=-60
Thread-4 produce(110) --> size=50

   

(某一次)運作結果:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
// LockTest2.java
// 仓库
class Depot {
  private int size;    // 仓库的实际数量
  private Lock lock;    // 独占锁
 
  public Depot() {
    this.size = 0;
    this.lock = new ReentrantLock();
  }
 
  public void produce(int val) {
//    lock.lock();
//    try {
      size += val;
      System.out.printf("%s produce(%d) --> size=%d\n",
          Thread.currentThread().getName(), val, size);
//    } catch (InterruptedException e) {
//    } finally {
//      lock.unlock();
//    }
  }
 
  public void consume(int val) {
//    lock.lock();
//    try {
      size -= val;
      System.out.printf("%s consume(%d) <-- size=%d\n",
          Thread.currentThread().getName(), val, size);
//    } finally {
//      lock.unlock();
//    }
  }
};
 
// 生产者
class Producer {
  private Depot depot;
 
  public Producer(Depot depot) {
    this.depot = depot;
  }
 
  // 消费产品:新建一个线程向仓库中生产产品。
  public void produce(final int val) {
    new Thread() {
      public void run() {
        depot.produce(val);
      }
    }.start();
  }
}
 
// 消费者
class Customer {
  private Depot depot;
 
  public Customer(Depot depot) {
    this.depot = depot;
  }
 
  // 消费产品:新建一个线程从仓库中消费产品。
  public void consume(final int val) {
    new Thread() {
      public void run() {
        depot.consume(val);
      }
    }.start();
  }
}
 
public class LockTest2 {
  public static void main(String[] args) {
    Depot mDepot = new Depot();
    Producer mPro = new Producer(mDepot);
    Customer mCus = new Customer(mDepot);
 
    mPro.produce(60);
    mPro.produce(120);
    mCus.consume(90);
    mCus.consume(150);
    mPro.produce(110);
  }
}

   

在「範例2」中,倉庫中最終剩餘的產品是-60,而不是我們期望的50。原因是我們沒有實現對倉庫的互斥存取。

2.3 範例3
在「範例3」中,我們透過Condition去解決「範例1」中的兩個問題:「倉庫的容量不可能為負數」以及「倉庫的容量是有限制的」。

解決問題是透過Condition。 Condition是需要和Lock共同使用的:透過Condition中的await()方法,能讓執行緒阻塞[類似wait()];透過Condition的signal()方法,能讓喚醒執行緒[類似於notify()]。

Thread-0 produce(60) --> size=-60
Thread-4 produce(110) --> size=50
Thread-2 consume(90) <-- size=-60
Thread-1 produce(120) --> size=-60
Thread-3 consume(150) <-- size=-60

   

(某一次)運算

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn