我的原則:先會用再說,內部慢慢來
一、 概念
屬於可重入鎖:可重入鎖,也叫做遞歸鎖,指的是同一線程外層函數獲得鎖之後 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。上一章節有講到。
二、 ReentrantLock 可重入
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class _08_01_TestReentrantLock {
Lock lock = new ReentrantLock();
public void print() throws InterruptedException {
System.out.println("do print");
lock.lock();
doAdd();
System.out.println("unlock do print");
lock.unlock();
}
public void doAdd() throws InterruptedException {
System.out.println("do add");
lock.lock();
System.out.println("unlock do add");
lock.unlock();
}
public static void main(String[] args) throws InterruptedException {
_08_01_TestReentrantLock testLock = new _08_01_TestReentrantLock();
testLock.print();
}
}
輸出:
do print
do add
unlock do add
unlock do print
講解: 同一線程外層函數獲得鎖之後 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響
三、 lock 與 unlock
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class _09_TestReentrantLock {
public static void main(String[] args){
Thread t1 = new Thread(new MyThread_Interrupted(),"AAAAAA");
Thread t2 = new Thread(new MyThread_Interrupted(),"BBBBBB");
t1.start();
t2.start();
// t2.interrupt();
}
}
class MyThread_Interrupted implements Runnable{
private static Lock lock = new ReentrantLock();
public void run(){
try{
System.out.println(Thread.currentThread().getName() + " wait to lock .");
lock.lock(); //中斷繼續等待
// lock.lockInterruptibly(); //中斷直接拋出異常
System.out.println(Thread.currentThread().getName() + " get the lock and running .");
TimeUnit.SECONDS.sleep(5);
}
catch (InterruptedException e){
System.out.println(Thread.currentThread().getName() + " interrupted .");
} finally {
System.out.println(Thread.currentThread().getName() + " finished . unlock ");
lock.unlock();
}
}
}
輸出:
BBBBBB wait to lock .
AAAAAA wait to lock .
BBBBBB get the lock and running .
BBBBBB finished . unlock
AAAAAA get the lock and running .
AAAAAA finished . unlock
結論:從結果上看,B 線程先拿到lock,然後A一直等待,知道B完成unlock,A纔拿到鎖
四、 lock.lock() 與 lock.lockinterruptibly()
- lock.lock() 等待拿到鎖,打斷我也繼續等,等到天荒地老。
- lock.lockinterruptibly() 接受你的打斷,然後拋異常。不等鎖了
重點:下面模擬一種情況: B 拿到鎖,一直沒釋放,A 在等待獲取鎖的過程中,遇到 Interrupted 的時候,會作出什麼反應。這也是 lock.lock() 與 lock.lockinterruptibly() 的區別。
代碼:
public class _09_TestReentrantLock {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(new MyThread_Interrupted(), "AAAAAA");
Thread t2 = new Thread(new MyThread_Interrupted(), "BBBBBB");
// 1. B 線程先跑起來
t2.start();
// sleep 確保 B 線程先走 ,A 線程再啓動
TimeUnit.SECONDS.sleep(1);
// 2. A 在 B 線程後面跑起來
t1.start();
// sleep 確保先 lock 操作,再來打斷
TimeUnit.SECONDS.sleep(1);
/*
3. A 線程現在依舊在等待,然後這個時候打斷。
lock.lock() 與 lock.lockInterruptibly() 的處理方式不同
*/
t1.interrupt();
System.out.println(" t1.interrupt() OK ...");
}
}
class MyThread_Interrupted implements Runnable {
private static ReentrantLock lock = new ReentrantLock();
private boolean hasLock = false;
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " wait to lock .");
lock.lock(); //被中斷,繼續等待
// lock.lockInterruptibly(); //被中斷,直接拋出異常
hasLock = true;
System.out.println(Thread.currentThread().getName() + " get the lock and running .");
// 重點:這個操作是模擬B線程一直在處理沒能釋放鎖,爲的是讓A一直等待着拿鎖。
if (Thread.currentThread().getName().equals("BBBBBB")) {
Scanner sc = new Scanner(System.in);
System.out.println("點擊任意鍵終止線程 ...");
sc.nextLine();
}
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + " be interrupted .");
} finally {
System.out.println(Thread.currentThread().getName() + ", has lock -> " + hasLock);
// 如果沒獲取到 lock ,然後去 unlock ,會跑出 IllegalMonitorStateException 異常
if(hasLock){
System.out.println(Thread.currentThread().getName() + " begin to unlock .");
lock.unlock();
System.out.println(Thread.currentThread().getName() + " finished . unlock ");
}
}
}
}
- lock.lock() 的輸出:
BBBBBB wait to lock .
BBBBBB get the lock and running .
點擊任意鍵終止線程 ...
AAAAAA wait to lock .
t1.interrupt() OK ...
// 這裏輸入任意鍵,目的是讓 B線程往下走,然後釋放鎖
BBBBBB, has lock -> true
BBBBBB begin to unlock .
BBBBBB finished . unlock
AAAAAA get the lock and running .
AAAAAA, has lock -> true
AAAAAA begin to unlock .
AAAAAA finished . unlock
- lock.lockInterruptibly() 的輸出:
BBBBBB wait to lock .
BBBBBB get the lock and running .
點擊任意鍵終止線程 ...
AAAAAA wait to lock .
t1.interrupt() OK ...
AAAAAA be interrupted .
AAAAAA, has lock -> false
// 這裏輸入任意鍵,目的是讓 B線程往下走,然後釋放鎖
BBBBBB, has lock -> true
BBBBBB begin to unlock .
BBBBBB finished . unlock
結論:
3. lock.lock() 忽略 interrupt() 操作,繼續阻塞等待獲取 lock .
4. lock.lockinterruptibly() 接受打斷,不去拿鎖了,直接拋 Exception
五、 trylock
嘗試獲取鎖,拿不到就直接返回 false
- boolean ReentrantLock#tryLock() :直接嘗試獲取鎖,返回true或者false
- boolean ReentrantLock#tryLock(long timeout, TimeUnit unit):直接嘗試獲取鎖,獲取不到重試一段時間。
六、 條件變量Condition 的使用
java.util.concurrent.locks.Condition
滿足某個條件Condition:線程可以跑,不滿足的話,線程掛起。
思考這麼一道題:編寫一個程序,開啓 3 個線程,這三個線程的 ID 分別爲 A、B、C,每個線程將自己的 ID 在屏幕上打印 10 遍,要求輸出的結果必須按順序顯示。
public class _12_TestABCAlternate_Condition {
public static void main(String[] args) {
AlternateDemo demo = new AlternateDemo();
new Thread(() -> {
for (int i = 1; i < 5; i++) {
demo.LoopA(i);
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i < 5; i++) {
demo.LoopB(i);
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i < 5; i++) {
demo.LoopC(i);
}
}, "C").start();
}
}
class AlternateDemo {
private int number = 1; //當前正在執行線程的標記
private ReentrantLock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
/**
* @param totalLoop : 循環第幾輪
*/
public void LoopA(int totalLoop) {
lock.lock();
try {
//1. 判斷
if (number != 1) {
condition1.await();
}
//2. 打印
System.out.println(Thread.currentThread().getName() + "-" + totalLoop);
//3. 喚醒
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void LoopB(int totalLoop) {
lock.lock();
try {
if (number != 2) {
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "-" + totalLoop);
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void LoopC(int totalLoop) {
lock.lock();
try {
if (number != 3) {
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "-" + totalLoop);
System.out.println("--------------------------------------------------");
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
輸出:
A-1
B-1
C-1
--------------------------------------------------
A-2
B-2
C-2
--------------------------------------------------
A-3
B-3
C-3
--------------------------------------------------
A-4
B-4
C-4
--------------------------------------------------
結論:滿足條件:run,不滿足條件 blocking