Java多線程程式設計-ReentrantLock使用及其源碼解析
- ReentrantLock簡單使用
-
- ReentrantLock實作等待通知
在java多線程開發中,我們可以使用 synchronized關鍵字來實作線程之間的同步互斥,我們也可以使用 ReentrantLock實作更多的功能,如嗅探鎖定。多路分支通知等功能,使用上會比synchronized更加靈活。
ReentrantLock簡單使用
可重入的互斥Lock,具有與使用sync方法和語句通路的隐式螢幕鎖相同的基本行為和語義。
由上一次成功鎖定但尚未解鎖的線程所有。當該鎖不屬于另一個線程時,調用lock的線程将傳回,并成功擷取該鎖。如果目前線程已經擁有該鎖,則該方法将立即傳回。可以使用方法 isHeldByCurrentThread和getHoldCount進行檢查。
public class ReentrantLockV1Main {
public static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
Thread thread01 = new Thread(() -> {
reentrantLock.lock();
try {
for (int i = 0; i < 5; i++) {
System.out.println("current thread name :" + Thread.currentThread().getName() + " value:" + (i + 1));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}, "thread01");
Thread thread02 = new Thread(() -> {
reentrantLock.lock();
try {
for (int i = 0; i < 5; i++) {
System.out.println("current thread name :" + Thread.currentThread().getName() + " value:" + (i + 1));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}, "thread02");
Thread thread03 = new Thread(() -> {
reentrantLock.lock();
try {
for (int i = 0; i < 5; i++) {
System.out.println("current thread name :" + Thread.currentThread().getName() + " value:" + (i + 1));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}, "thread03");
thread01.start();
thread02.start();
thread03.start();
}
}
運作結果:
使用lock方法擷取鎖,使用unlock方法釋放鎖。每個線程同步執行。
ReentrantLock實作等待通知
我們知道synchronized和wait/notify可以實作等待通知模式,ReentrantLock也可以實作,但是要借助Condition對象。在一個lock對象裡面可以建立多個condition執行個體,線程對象可以注冊在指定的condition中,進而可以有選擇性的進行線程的通知。在排程線程上更加靈活。
在沒有獲得鎖的情況下,進行wait操作會報錯:
public class ReentranLockObj1 {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void waitFun() {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
線程類:
public class ReentranLockObj1T1 extends Thread {
private ReentranLockObj1 reentranLockObj1;
public ReentranLockObj1T1(ReentranLockObj1 reentranLockObj1) {
this.reentranLockObj1 = reentranLockObj1;
}
@Override
public void run() {
reentranLockObj1.waitFun();
}
}
運作結果:
我們看看該類的Condition接口源碼:
public interface Condition {
//使目前線程等待,直到發出信号或被中斷
void await() throws InterruptedException;
//使目前線程等待,直到發出信号
void awaitUninterruptibly();
//使目前線程等待,直到發出信号或被中斷或經過指定的等待時間為止
long awaitNanos(long nanosTimeout) throws InterruptedException;
//使目前線程等待,直到發出信号或被中斷,或者經過指定的等待時間
boolean await(long time, TimeUnit unit) throws InterruptedException;
//使目前線程等待,直到發出信号或被中斷,或者經過指定的截止時間
boolean awaitUntil(Date deadline) throws InterruptedException;
//喚醒一個等待的線程
void signal();
//喚醒所有的等待線程
void signalAll();
}
我們隻需要将ReentranLockObj1 waitFun方法中添加lock即可,修改如下:
public void waitFun() {
lock.lock();
try {
System.out.println("current thread name :" + Thread.currentThread().getName());
condition.await();
System.out.println("current thread name :" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
看看運作結果:
await方法後面的語句沒有輸入到控制台,處于等待狀态。
進行通知喚醒:
public class ReentranLockObj2 {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
/**
* 等待
*/
public void waitFun() {
lock.lock();
try {
System.out.println("current thread name:" + Thread.currentThread().getName());
condition.await();
System.out.println("current thread name:" + Thread.currentThread().getName());
} catch (IllegalMonitorStateException | InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 喚醒
*/
public void notifyFun() {
lock.lock();
try {
condition.signal();
} catch (IllegalMonitorStateException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ReentranLockObj1T2Main {
public static void main(String[] args) throws InterruptedException {
ReentranLockObj2 reentranLockObj2 = new ReentranLockObj2();
ReentranLockObj2T2 reentranLockObj2T2 = new ReentranLockObj2T2(reentranLockObj2);
reentranLockObj2T2.start();
Thread.sleep(1000);
reentranLockObj2.notifyFun();
}
}
運作結果:
列印第一句,1秒之後列印第二句,使用signal()喚醒。喚醒所有等待中的線程,使用signalAll()
public class ReentranLockObj3 {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void waitFun1() {
lock.lock();
try {
System.out.println("current thread name:" + Thread.currentThread().getName());
condition.await();
System.out.println("current thread name:" + Thread.currentThread().getName());
} catch (IllegalMonitorStateException | InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void waitFun2() {
lock.lock();
try {
System.out.println("current thread name:" + Thread.currentThread().getName());
condition.await();
System.out.println("current thread name:" + Thread.currentThread().getName());
} catch (IllegalMonitorStateException | InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void notifyAllFun() {
lock.lock();
try {
condition.signalAll();
} catch (IllegalMonitorStateException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
線程1:
public class ReentranLockObj3T1 extends Thread {
private ReentranLockObj3 reentranLockObj3;
public ReentranLockObj3T1(ReentranLockObj3 reentranLockObj3, String threadName) {
this.reentranLockObj3 = reentranLockObj3;
this.setName(threadName);
}
@Override
public void run() {
reentranLockObj3.waitFun1();
}
}
線程2:
public class ReentranLockObj3T2 extends Thread {
private ReentranLockObj3 reentranLockObj3;
public ReentranLockObj3T2(ReentranLockObj3 reentranLockObj3, String threadName) {
this.reentranLockObj3 = reentranLockObj3;
this.setName(threadName);
}
@Override
public void run() {
reentranLockObj3.waitFun2();
}
}
運作結果:
先輸出上面兩行,等5分鐘通過調用signalAll進行喚醒所有等待中的線程。
如果想單獨喚醒一個,該怎麼處理呢? 我們需要建立多個condition進行處理了。通過condition喚醒部分線程,可以有效提高程式運作效率。
public class ReentranLockObj4 {
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
public void waitFunCondition1() {
lock.lock();
try {
System.out.println("current thread name:" + Thread.currentThread().getName());
condition1.await();
System.out.println("current thread name:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void waitFunCondition2() {
lock.lock();
try {
System.out.println("current thread name:" + Thread.currentThread().getName());
condition2.await();
System.out.println("current thread name:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void notifyFunCondition1() {
lock.lock();
try {
condition1.signalAll();
} catch (IllegalMonitorStateException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void notifyFunCondition2() {
lock.lock();
try {
condition2.signalAll();
} catch (IllegalMonitorStateException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
線程1:
public class ReentranLockObj4T1 extends Thread {
private ReentranLockObj4 reentranLockObj4;
public ReentranLockObj4T1(ReentranLockObj4 reentranLockObj4, String threadName) {
this.reentranLockObj4 = reentranLockObj4;
this.setName(threadName);
}
@Override
public void run() {
reentranLockObj4.waitFunCondition1();
}
}
線程2:
public class ReentranLockObj4T2 extends Thread {
private ReentranLockObj4 reentranLockObj4;
public ReentranLockObj4T2(ReentranLockObj4 reentranLockObj4, String threadName) {
this.reentranLockObj4 = reentranLockObj4;
this.setName(threadName);
}
@Override
public void run() {
reentranLockObj4.waitFunCondition2();
}
}
運作:
public class ReentranLockObj4Main {
public static void main(String[] args) throws InterruptedException {
ReentranLockObj4 reentranLockObj4 = new ReentranLockObj4();
ReentranLockObj4T1 reentranLockObj4T1 = new ReentranLockObj4T1(reentranLockObj4, "thread01");
reentranLockObj4T1.start();
ReentranLockObj4T2 reentranLockObj4T2 = new ReentranLockObj4T2(reentranLockObj4, "thread02");
reentranLockObj4T2.start();
Thread.sleep(5000);
reentranLockObj4.notifyFunCondition1();
}
}
運作結果:
有運作可以看出,當線程1喚醒的時候,調用線程1的wait後的方法,線程2沒有調用,就還在等待中,程式沒有結束。由此可以得出結論,ReentrantLock可以喚醒指定種類的線程。
總結一下:
1.Object類中的wait相當于Condition中的await
2.Object類中的wait(long timeout) 相當于Condition中的await(long timeout,TimeUnit unit)
3.Object類中的notify相當于Condition中的signal
4.Object類中的notifyAll相當于Condition中的signalAll