【線程】ReentrantLock 實戰 (七)

我的原則:先會用再說,內部慢慢來


一、 概念

屬於可重入鎖:可重入鎖,也叫做遞歸鎖,指的是同一線程外層函數獲得鎖之後 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。上一章節有講到。

二、 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()

  1. lock.lock() 等待拿到鎖,打斷我也繼續等,等到天荒地老。
  2. 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 ");
            }
        }
    }
}


  1. 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 

  1. 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

  1. boolean ReentrantLock#tryLock() :直接嘗試獲取鎖,返回true或者false
  2. 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

七、 番外篇

上一章節:【線程】可重入鎖與不可重入鎖(六)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章