1. 介紹
- ReentrantLock顧名思義就是鎖可以重入,ReentrantLock持有一個所計數(shù)器,當已持有所的線程再次獲得該鎖時計數(shù)器值加1,每調(diào)用一次lock.unlock()時所計數(shù)器值減一,直到所計數(shù)器值為0,此時線程釋放鎖。
例如:一個線程持有鎖,state=1,如果它再次調(diào)用lock方法,那么他將繼續(xù)擁有這把鎖,state=2。當前可重入鎖要完全釋放,調(diào)用了多少次lock方法,還得調(diào)用等量的unlock方法來完全釋放鎖。
2. API使用
-
lock()\unlock() 加鎖與釋放鎖
public class ReentrantLockTest {
private Lock lock = new ReentrantLock();
public void testReentrantLock() throws InterruptedException {
// 線程獲得鎖
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " get lock");
Thread.sleep(100L);
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " get lock again");
}finally {
// 線程釋放鎖
lock.unlock();
System.out.println(Thread.currentThread().getName() + " release lock");
}
} finally {
// 線程釋放鎖
lock.unlock();
System.out.println(Thread.currentThread().getName() + " release lock again");
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
final ReentrantLockTest test2 = new ReentrantLockTest();
Thread thread = new Thread(new Runnable(){
public void run() {
try {
test2.testReentrantLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A");
thread.start();
}
}
-
測試鎖與超時
- lock(), 拿不到lock就不罷休,不然線程就一直block。 比較無賴的做法。
- tryLock(),馬上返回,拿到lock就返回true,不然返回false。 比較瀟灑的做法。
- tryLock(long time, TimeUnit unit),帶時間限制拿不到lock,就等一段時間,超時返回false。比較聰明的做法。
- lockInterruptibly(),線程在sleep或wait,join, 此時如果別的進程調(diào)用此進程的 interrupt()方法,此線程會被喚醒并被要求處理InterruptedException。
代碼示例:
public void lockTest() {
// 當前線程在鎖可用時直接獲得鎖,鎖不可用時阻塞當前線程
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " lock get lock");
long beginTime = System.currentTimeMillis();
while (System.currentTimeMillis() - beginTime < 1000) {}
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + " lock release lock");
}
}
public void tryLockTest() throws InterruptedException {
long beginTime = System.currentTimeMillis();
while(System.currentTimeMillis() - beginTime <100) {}
// 當前線程嘗試獲得鎖,如果獲得鎖返回true,否則返回false
if(lock.tryLock()) {
try{
System.out.println(Thread.currentThread().getName() + " tryLock get lock");
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + " tryLock release lock");
}
} else {
System.out.println(Thread.currentThread().getName() + " tryLock can not get lock");
}
}
- lock方法不能被中斷。如果一個線程在等待獲得一個鎖時被中斷,中斷線程在獲得鎖之前會一直處于 阻塞狀態(tài)。如果出現(xiàn)死鎖,那么lock方法就無法被終止。但是tryLock(long time, TimeUnit unit)和lockInterruptibly方法在申請鎖的過程中是可以被中斷的。
public void tryLockInterruptTest() {
long beginTime = System.currentTimeMillis();
while(System.currentTimeMillis() - beginTime <100) {}
try {
//會拋出InterruptedException異常,需要手動處理線程中斷情況。lock.lockInterruptibly()一樣
if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
try{
System.out.println(Thread.currentThread().getName() + " tryLock get lock");
}finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println(Thread.currentThread().getName() + " was interrupted");
}
}
-
條件對象
- 通常,線程進入臨界區(qū),卻發(fā)現(xiàn)在某一條件滿足之后才能執(zhí)行,條件對象就是用來管理那些已經(jīng)獲得了鎖,但是卻不能做有用工作的線程。
- 一個鎖對象可以有一個或多個相關(guān)的條件對象,我們可用lock.newCondition()方法獲得一個條件對象。
- 與對象的wait()/notify()作用一樣
下面我們直接通過代碼來學習條件對象的使用。
以下代碼實現(xiàn)的是簡單的生產(chǎn)者和消費者案例:
package com.jd.coutdownlatch;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockTest3 {
private ReentrantLock myLock = new ReentrantLock();
private Condition condition = myLock.newCondition();
private List<Integer> listBuffer = new ArrayList<Integer>();
private volatile boolean runFlag = true;
/**
* 生產(chǎn)者 生產(chǎn)數(shù)據(jù)
*/
public void produce() {
int i = 0;
while (runFlag) {
myLock.lock();
try {
// 生產(chǎn)者檢查容器中是否有數(shù)據(jù),如果容器中有數(shù)據(jù)則生產(chǎn)者等待
// 如果容器中沒有數(shù)據(jù)則生產(chǎn)數(shù)據(jù)放入容器中并通知消費者
if (listBuffer.size() > 0) {
try {
// 調(diào)用await()方法,生產(chǎn)者線程阻塞并釋放鎖,之后進入該條件的等待集中
// 直到消費者調(diào)用signalAll()方法之后,生產(chǎn)者線程解除阻塞并重新競爭鎖
// 生產(chǎn)者線程獲得鎖之后,重新開始從被阻塞的地方繼續(xù)執(zhí)行程序
condition.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + " add Integer");
listBuffer.add(i++);
// 生產(chǎn)者線程調(diào)用signalAll()方法,通知消費者線程容器中有數(shù)據(jù)
condition.signalAll();
}
} finally {
myLock.unlock();
}
}
}
/**
* 消費者 讀取數(shù)據(jù)
*/
public void consume() {
while (runFlag) {
myLock.lock();
try {
// 消費者檢查容器中是否有數(shù)據(jù),如果沒有數(shù)據(jù)消費者等待
// 如果容器中有數(shù)據(jù)則讀取數(shù)據(jù),讀完之后通知生產(chǎn)者
if (listBuffer.size() == 0) {
try {
// 同生產(chǎn)者線程一樣,消費者線程調(diào)用await()方法阻塞并進入該條件等待集中
condition.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + " get Integer");
long beginTime = 0;
System.out.println(listBuffer.remove(0));
beginTime = System.currentTimeMillis();
while (System.currentTimeMillis() - beginTime < 100) {
}
// 消費者線程調(diào)用signalAll()方法,通知生產(chǎn)者生產(chǎn)數(shù)據(jù)
condition.signalAll();
}
} finally {
myLock.unlock();
}
}
}
public boolean isRunFlag() {
return runFlag;
}
public void setRunFlag(boolean runFlag) {
this.runFlag = runFlag;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
final ReentrantLockTest3 test = new ReentrantLockTest3();
Thread produce = new Thread(new Runnable() {
public void run() {
test.produce();
}
}, "A");
Thread consume = new Thread(new Runnable() {
public void run() {
test.consume();
}
}, "B");
produce.start();
consume.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
test.setRunFlag(false);
}
}