【併發編程】:鎖 !!!!!

一、

1. wait / notify

在這裏插入圖片描述
1.WAITING和TIMED_WAITING
WAITING:是獲得鎖之後,放棄了鎖,條件不滿足的等待,是永久等待,除非notify喚醒,進入WaitSet;
TIMED_WAITING:有時效性的等待,如sleep會進入該阻塞狀態;
BLOCKED:該阻塞狀態是對象還沒有獲得鎖,在阻塞隊列EntryList中等待。
在這裏插入圖片描述
【它們都是線程之間進行協作的手段,都屬於 Object 對象的方法。必須獲得此對象的鎖,才能調用這幾個方法】

wait() 方法會釋放對象的鎖,進入 WaitSet 等待區,從而讓其他線程就機會獲取對象的鎖。無限制等待,直到notify 爲止
wait(long n) 有時限的等待, 到 n 毫秒後結束等待繼續向下執行,或是被 notify喚醒,沒有等夠n毫秒也繼續向下執行
(1) 進入Object監視器的線程才能調用wait()方法:

@Slf4j(topic = "c.Test18")
public class Test18 {
    static final Object lock = new Object();
    
    public static void main(String[] args) {
        //只有成爲了owner(獲得了鎖對象)才能調用wait()方法
        synchronized (lock) {
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

(2) 使用notify()喚醒等待區的一個線程 或 notifyAll()喚醒等待區所有的線程:

@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
    final static Object obj = new Object();
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (obj) {
                log.debug("執行....");
                try {
                    obj.wait(); // 讓線程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //被喚醒之後繼續執行這個代碼
                log.debug("其它代碼....");
            }
        },"t1").start();

        new Thread(() -> {
            synchronized (obj) {
                log.debug("執行....");
                try {
                    obj.wait(); // 讓線程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //被喚醒之後繼續執行這個代碼
                log.debug("其它代碼....");
            }
        },"t2").start();

        // 主線程兩秒後執行
        sleep(2);
        log.debug("喚醒 obj 上其它線程");
        synchronized (obj) {
//            obj.notify(); // 喚醒obj上一個線程
            obj.notifyAll(); // 喚醒obj上所有等待線程
        }
    }
}

notifyAll()的執行結果:

20:41:39.046 c.TestWaitNotify [t2] - 執行....
20:41:39.080 c.TestWaitNotify [t1] - 執行....
20:41:41.037 c.TestWaitNotify [main] - 喚醒 obj 上其它線程
20:41:41.037 c.TestWaitNotify [t1] - 其它代碼....
20:41:41.037 c.TestWaitNotify [t2] - 其它代碼....

notify()的執行結果:

20:42:39.865 c.TestWaitNotify [t1] - 執行....
20:42:39.874 c.TestWaitNotify [t2] - 執行....
20:42:41.863 c.TestWaitNotify [main] - 喚醒 obj 上其它線程
20:42:41.864 c.TestWaitNotify [t1] - 其它代碼....

2. wait/notify的使用

1、wait(n)與sleep(n)的區別

(1) 開始之前先看看sleep(long n) 和 wait(long n) 的區別:

  1. sleep 是 Thread 方法,而 wait 是 Object 的方法
  2. sleep 不需要強制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
  3. sleep 在睡眠的同時,不會釋放對象鎖的,但 wait 在等待的時候會釋放對象鎖
  4. 線程狀態相同: TIMED_WAITING
@Slf4j(topic = "c.Test19")
public class Test19 {
    static final Object lock = new Object();
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock) {
                log.debug("獲得鎖");
                try {
                    Thread.sleep(20000);
                  //  lock.wait(20000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1").start();
        
        Sleeper.sleep(1);
        //主線程想要獲取lock鎖
        synchronized (lock) {
            log.debug("獲得鎖");
        }
    }
}

執行sleep()方法後的結果:在線程t1睡眠期間,主線程沒有獲得鎖

20:57:34.663 c.Test19 [t1] - 獲得鎖

執行wait()方法後的結果:線程t1等待20s,在線程t1等待期間,主線程獲得了鎖

21:00:37.399 c.Test19 [t1] - 獲得鎖
21:00:38.397 c.Test19 [main] - 獲得鎖
2、使用過程

step1:

@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep1 {
    static final Object room = new Object();
    static boolean hasCigarette = false; // 有沒有煙
    static boolean hasTakeout = false;

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (room) {
                log.debug("有煙沒?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("沒煙,先歇會!");
                    sleep(2);
                }
                log.debug("有煙沒?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以開始幹活了");
                }
            }
        }, "小南").start();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (room) {
                    log.debug("可以開始幹活了");
                }
            }, "其它人").start();
        }

        sleep(1);
        new Thread(() -> {
            // 這裏能不能加 synchronized (room)?
//            synchronized (room) {
                  hasCigarette = true;
//                log.debug("煙到了噢!");
//            }
        }, "送煙的").start();
    }
}
21:19:26.338 c.TestCorrectPosture [小南] - 有煙沒?[false]
21:19:26.345 c.TestCorrectPosture [小南] - 沒煙,先歇會!
21:19:28.345 c.TestCorrectPosture [小南] - 有煙沒?[true]
21:19:28.345 c.TestCorrectPosture [小南] - 可以開始幹活了
21:19:28.346 c.TestCorrectPosture [其它人] - 可以開始幹活了
21:19:28.347 c.TestCorrectPosture [其它人] - 可以開始幹活了
21:19:28.348 c.TestCorrectPosture [其它人] - 可以開始幹活了
21:19:28.348 c.TestCorrectPosture [其它人] - 可以開始幹活了
21:19:28.349 c.TestCorrectPosture [其它人] - 可以開始幹活了

使用sleep()方法的缺陷:
在這裏插入圖片描述step2:

@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep2 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (room) {
                log.debug("有煙沒?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("沒煙,先歇會!");
                    try {
                        room.wait();
                        //如果使用interrupt()方法打斷等待中的線程會拋出線程,捕獲異常後可以繼續向下執行
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有煙沒?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以開始幹活了");
                }
            }
        }, "小南").start();

        //其他人對應的線程
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (room) {
                    log.debug("可以開始幹活了");
                }
            }, "其它人").start();
        }

        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasCigarette = true;
                log.debug("煙到了噢!");
                //notify()方法也必須放在synchronized中,因爲它也需要先擁有鎖對象,才能執行
                room.notify();
            }
        }, "送煙的").start();
    }
}
21:27:50.830 c.TestCorrectPosture [其它人] - 可以開始幹活了
21:27:50.835 c.TestCorrectPosture [其它人] - 可以開始幹活了
21:27:50.836 c.TestCorrectPosture [小南] - 有煙沒?[false]
21:27:50.841 c.TestCorrectPosture [小南] - 沒煙,先歇會!
21:27:50.846 c.TestCorrectPosture [其它人] - 可以開始幹活了
21:27:50.849 c.TestCorrectPosture [其它人] - 可以開始幹活了
21:27:50.849 c.TestCorrectPosture [其它人] - 可以開始幹活了
21:27:51.823 c.TestCorrectPosture [送煙的] - 煙到了噢!
21:27:51.824 c.TestCorrectPosture [小南] - 有煙沒?[true]
21:27:51.824 c.TestCorrectPosture [小南] - 可以開始幹活了

解決了其他線程阻塞問題,但是如果有其他線程也在等待呢?就是說等待的線程不止小南一個,那麼會不會喚醒錯了呢?

step3:

@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep3 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    // 虛假喚醒
    public static void main(String[] args) {
        //小南線程等待煙
        new Thread(() -> {
            synchronized (room) {
                log.debug("有煙沒?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("沒煙,先歇會!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有煙沒?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以開始幹活了");
                } else {
                    log.debug("沒幹成活...");
                }
            }
        }, "小南").start();

        //小女線程等待外賣
        new Thread(() -> {
            synchronized (room) {
                Thread thread = Thread.currentThread();
                log.debug("外賣送到沒?[{}]", hasTakeout);
                if (!hasTakeout) {
                    log.debug("沒外賣,先歇會!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("外賣送到沒?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("可以開始幹活了");
                } else {
                    log.debug("沒幹成活...");
                }
            }
        }, "小女").start();

        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                //外賣到了
                hasTakeout = true;
                log.debug("外賣到了噢!");
                //隨機從等待區叫醒一個線程
                room.notify();
            }
        }, "送外賣的").start();
    }
}

送外賣的應該叫醒小南但是卻把小女叫醒了:

21:36:20.549 c.TestCorrectPosture [小南] - 有煙沒?[false]
21:36:20.555 c.TestCorrectPosture [小南] - 沒煙,先歇會!
21:36:20.555 c.TestCorrectPosture [小女] - 外賣送到沒?[false]
21:36:20.555 c.TestCorrectPosture [小女] - 沒外賣,先歇會!
21:36:21.549 c.TestCorrectPosture [送外賣的] - 外賣到了噢!
21:36:21.550 c.TestCorrectPosture [小南] - 有煙沒?[false]
21:36:21.550 c.TestCorrectPosture [小南] - 沒幹成活...

notify 只能隨機喚醒一個 WaitSet 中的線程,這時如果有其它線程也在等待,那麼就可能喚醒不了正確的線程,稱之爲【虛假喚醒】。

step4: 解決方法:改爲 notifyAll

21:56:42.864 c.TestCorrectPosture [小南] - 有煙沒?[false]
21:56:42.898 c.TestCorrectPosture [小南] - 沒煙,先歇會!
21:56:42.899 c.TestCorrectPosture [小女] - 外賣送到沒?[false]
21:56:42.900 c.TestCorrectPosture [小女] - 沒外賣,先歇會!
21:56:43.850 c.TestCorrectPosture [送外賣的] - 外賣到了噢!
21:56:43.851 c.TestCorrectPosture [小女] - 外賣送到沒?[true]
21:56:43.851 c.TestCorrectPosture [小女] - 可以開始幹活了
21:56:43.852 c.TestCorrectPosture [小南] - 有煙沒?[false]
21:56:43.852 c.TestCorrectPosture [小南] - 沒幹成活...

從結果可以看出小南幹成活,小女幹成活了:
用 notifyAll 僅解決某個線程的喚醒問題,但使用 if + wait 判斷僅有一次機會,一旦條件不成立,就沒有重新判斷的機會了

step5: 解決方法,用 while + wait,當條件不成立,再次 wait,什麼時候條件成立了才往下走,就是說只要送的不是煙而是外賣我就繼續等待

@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep5 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (room) {
                log.debug("有煙沒?[{}]", hasCigarette);
                //條件不成立,就會進行下一輪的等待,而不會向下執行
                while (!hasCigarette) {
                    log.debug("沒煙,先歇會!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有煙沒?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以開始幹活了");
                } else {
                    log.debug("沒幹成活...");
                }
            }
        }, "小南").start();

        new Thread(() -> {
            synchronized (room) {
                Thread thread = Thread.currentThread();
                log.debug("外賣送到沒?[{}]", hasTakeout);
                while (!hasTakeout) {
                    log.debug("沒外賣,先歇會!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("外賣送到沒?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("可以開始幹活了");
                } else {
                    log.debug("沒幹成活...");
                }
            }
        }, "小女").start();

        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外賣到了噢!");
                room.notifyAll();
            }
        }, "送外賣的").start();
    }
}
22:05:09.560 c.TestCorrectPosture [小南] - 有煙沒?[false]
22:05:09.578 c.TestCorrectPosture [小南] - 沒煙,先歇會!
22:05:09.578 c.TestCorrectPosture [小女] - 外賣送到沒?[false]
22:05:09.578 c.TestCorrectPosture [小女] - 沒外賣,先歇會!
22:05:10.562 c.TestCorrectPosture [送外賣的] - 外賣到了噢!
22:05:10.563 c.TestCorrectPosture [小女] - 外賣送到沒?[true]
22:05:10.563 c.TestCorrectPosture [小女] - 可以開始幹活了
22:05:10.564 c.TestCorrectPosture [小南] - 沒煙,先歇會!

使用wait / notify總結:
在這裏插入圖片描述

3. 保護性暫停模式

在這裏插入圖片描述

4. 生產者消費者模式

在這裏插入圖片描述

@Slf4j(topic = "c.Test21")
public class Test21 {

    public static void main(String[] args) {
        MessageQueue queue = new MessageQueue(2);

        for (int i = 0; i < 3; i++) {
            int id = i;
            new Thread(() -> {
                queue.put(new Message(id , "值"+id));
            }, "生產者" + i).start();
        }

        new Thread(() -> {
            while(true) {
                sleep(1);
                Message message = queue.take();
            }
        }, "消費者").start();
    }

}

// 消息隊列類 , java 線程之間通信
@Slf4j(topic = "c.MessageQueue")
class MessageQueue {
    // 消息的隊列集合,雙向鏈表
    private LinkedList<Message> list = new LinkedList<>();

    // 隊列容量
    private int capcity;

    public MessageQueue(int capcity) {
        this.capcity = capcity;
    }

    // 獲取消息
    public Message take() {
        // 檢查隊列是否爲空
        synchronized (list) {
            //喚醒後判斷隊列不爲空,就會向下執行取消息
            while(list.isEmpty()) {
                try {
                    log.debug("隊列爲空, 消費者線程等待");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 從隊列頭部獲取消息並返回
            Message message = list.removeFirst();
            log.debug("已消費消息 {}", message);
            list.notifyAll();
            return message;
        }
    }

    // 存入消息
    public void put(Message message) {
        synchronized (list) {
            // 檢查對象是否已滿
            //喚醒後判斷隊列沒滿就會繼續存消息
            while(list.size() == capcity) {
                try {
                    log.debug("隊列已滿, 生產者線程等待");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 將消息加入隊列尾部
            list.addLast(message);
            log.debug("已生產消息 {}", message);
            //讓等待中的線程都喚醒
            list.notifyAll();
        }
    }
}

@Data
@Getter
@AllArgsConstructor
final class Message {
    private int id;
    private Object value;
}

結果:

22:51:18.539 c.MessageQueue [生產者0] - 已生產消息 Message(id=0, value=0)
22:51:18.561 c.MessageQueue [生產者1] - 已生產消息 Message(id=1, value=1)
22:51:18.562 c.MessageQueue [生產者2] - 隊列已滿, 生產者線程等待
22:51:19.549 c.MessageQueue [消費者] - 已消費消息 Message(id=0, value=0)
22:51:19.549 c.MessageQueue [生產者2] - 已生產消息 Message(id=2, value=2)
22:51:20.549 c.MessageQueue [消費者] - 已消費消息 Message(id=1, value=1)
22:51:21.549 c.MessageQueue [消費者] - 已消費消息 Message(id=2, value=2)
22:51:22.550 c.MessageQueue [消費者] - 隊列爲空, 消費者線程等待

二、park / unpark

在這裏插入圖片描述

public class TestParkUnpark {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("start...");
            sleep(2);
            log.debug("park...");

            LockSupport.park();
            log.debug("resume...");
        }, "t1");
        t1.start();

        sleep(1);
        log.debug("unpark...");
        LockSupport.unpark(t1);
    }
}

結果:

22:56:48.547 c.TestParkUnpark [t1] - start...
22:56:49.537 c.TestParkUnpark [main] - unpark...
22:56:50.648 c.TestParkUnpark [t1] - park...
22:56:50.648 c.TestParkUnpark [t1] - resume...

與 Object 的 wait & notify 相比:

  • park & unpark 是以線程爲單位來【阻塞】和【喚醒】線程,而 notify 只能隨機喚醒一個等待線程,notifyAll是喚醒所有等待線程,就不那麼【精確】
  • wait,notify 和 notifyAll 必須配合 Object Monitor 一起使用,而 park,unpark 不必
  • park & unpark 可以先 unpark,而 wait & notify 不能先 notify

三、 線程狀態裝換

數字標號就代表下面的情況 數字
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述

四、多把鎖

看下面的一個線程代碼:一間大屋子有兩個功能:睡覺、學習,互不相干。
現在小南要學習,小女要睡覺,但如果只用一間屋子(一個對象鎖)的話,那麼併發度很低

public class TestMultiLock {
    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();
        new Thread(() -> {
            bigRoom.study();
        },"小南").start();
        new Thread(() -> {
            bigRoom.sleep();
        },"小女").start();
    }
}

@Slf4j(topic = "c.BigRoom")
class BigRoom {
    public void sleep() {
        synchronized (this) {
            log.debug("sleeping 2 小時");
            Sleeper.sleep(2);
        }
    }

    public void study() {
        synchronized (this) {
            log.debug("study 1 小時");
            Sleeper.sleep(1);
        }
    }
}

當小南睡覺的時候佔有着鎖,小女就不能學習,解決的方法是使用多把鎖:

public class TestMultiLock {
    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();
        new Thread(() -> {
            bigRoom.study();
        },"小南").start();
        new Thread(() -> {
            bigRoom.sleep();
        },"小女").start();
    }
}

@Slf4j(topic = "c.BigRoom")
class BigRoom {

    private final Object studyRoom = new Object();

    private final Object bedRoom = new Object();

    public void sleep() {
        synchronized (bedRoom) {
            log.debug("sleeping 2 小時");
            Sleeper.sleep(2);
        }
    }

    public void study() {
        synchronized (studyRoom) {
            log.debug("study 1 小時");
            Sleeper.sleep(1);
        }
    }
}

將鎖的粒度細分:
 好處,是可以增強併發度
 壞處,如果一個線程需要同時獲得多把鎖,就容易發生死鎖

五、線程活躍性

死鎖:

有這樣的情況:一個線程需要同時獲取多把鎖,這時就容易發生死鎖
t1 線程 獲得 A對象 鎖,接下來想獲取 B對象 的鎖
t2 線程 獲得 B對象 鎖,接下來想獲取 A對象 的鎖

@Slf4j(topic = "c.TestDeadLock")
public class TestDeadLock {
    public static void main(String[] args) {
        test1();
    }

    private static void test1() {
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (A) {
                log.debug("lock A");
                sleep(1);
                synchronized (B) {
                    log.debug("lock B");
                    log.debug("操作...");
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (B) {
                log.debug("lock B");
                sleep(0.5);
                synchronized (A) {
                    log.debug("lock A");
                    log.debug("操作...");
                }
            }
        }, "t2");
        t1.start();
        t2.start();
    }
}

活鎖:

活鎖出現在兩個線程互相改變對方的結束條件,最後誰也無法結束,例如

@Slf4j(topic = "c.TestLiveLock")
public class TestLiveLock {
    static volatile int count = 10;
    static final Object lock = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            // 期望減到 0 退出循環
            while (count > 0) {
                sleep(0.2);
                count--;
                log.debug("count: {}", count);
            }
        }, "t1").start();
        new Thread(() -> {
            // 期望超過 20 退出循環
            while (count < 20) {
                sleep(0.2);
                count++;
                log.debug("count: {}", count);
            }
        }, "t2").start();
    }
}

六、ReentrantLock

synchronized與ReentrantLock的區別:
在這裏插入圖片描述

1. 可重入

可重入是指同一個線程如果首次獲得了這把鎖,那麼因爲它是這把鎖的擁有者,因此有權利再次獲取這把鎖。如果是不可重入鎖,那麼第二次獲得鎖時,自己也會被鎖擋住

public class hh {

    //創建鎖對象
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        method1();
    }
    public static void method1() {
        //給m1方法加了鎖
        lock.lock();
        try {
            log.debug("execute method1");
            //m1還沒解鎖,又去調用m2
            method2();
        } finally {
            lock.unlock();
        }
    }
    public static void method2() {
        //給m2加鎖
        lock.lock();
        try {
            log.debug("execute method2");
            method3();
        } finally {
            lock.unlock();
        }
    }
    public static void method3() {
        lock.lock();
        try {
            log.debug("execute method3");
        } finally {
            lock.unlock();
        }
    }
}

2. 可打斷

可以避免無限制的等待鎖,被動的避免死等:lock.lockInterruptibly()

public class hh {
    ReentrantLock lock = new ReentrantLock();
    Thread t1 = new Thread(() -> {
        log.debug("啓動...");
        try {
            //可打斷鎖
            //如果沒有競爭,此方法就會獲的Lock對象鎖
            //如果有競爭進入阻塞隊列等待,可以被其他線程的interrupt方法打斷,打斷後不再等待直接進入catch語句塊
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            e.printStackTrace();
            log.debug("等鎖的過程中被打斷");
            return;
        }
        
        try {
            log.debug("獲得了鎖");
        } finally {
            lock.unlock();
        }
    }, "t1");

    //主線程先獲了鎖,t1線程就會進入阻塞隊列進行等待
    lock.lock();
    log.debug("獲得了鎖");
    
    t1.start();
    
    try{
        //主線程睡眠
        sleep(1);
        //t1線程等待時,可以打斷,讓其不再等待,直接向下指向catch語句塊,不再獲取鎖
        t1.interrupt();
    } finally{
        lock.unlock();
    }
}

在這裏插入圖片描述注意如果是不可中斷模式,那麼即使使用了 interrupt 也不會讓等待中斷:lock.lock()

public class hh {
    ReentrantLock lock = new ReentrantLock();
    Thread t1 = new Thread(() -> {
        log.debug("啓動...");
        try {
            //注意如果是不可中斷模式,那麼即使使用了interrupt也不會讓等待中斷
            lock.lock();
        } catch (InterruptedException e) {
            e.printStackTrace();
            log.debug("等鎖的過程中被打斷");
            return;
        }

        try {
            log.debug("獲得了鎖");
        } finally {
            lock.unlock();
        }
    }, "t1");

    lock.lock();
    log.debug("獲得了鎖");

    t1.start();

    try{
        //主線程睡眠
        sleep(1);
        t1.interrupt();
    } finally{
        lock.unlock();
    }
}

在這裏插入圖片描述

3. 鎖超時

lock.tryLock()
lock.tryLock(2, TimeUnit.SECONDS)

@Slf4j(topic = "c.Test22")
public class Test22 {
    //創建鎖對象
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("嘗試獲得鎖");
            try {
                //嘗試獲得鎖,成功爲true,失敗爲false
                //如果爲false,不用向下執行
                //等待2s
                if (! lock.tryLock(2, TimeUnit.SECONDS)) {
                    log.debug("獲取不到鎖");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("獲取不到鎖");
                return;
            }
            
            //如果爲true
            try {
                log.debug("獲得到鎖");
            } finally {
                lock.unlock();
            }
        }, "t1");
        
        //主線程一直持有鎖
        lock.lock();
        log.debug("獲得到鎖");
        t1.start();
        
        //主線程1s後釋放了鎖,t1線程等待2s,在等待時間內獲得了鎖
        sleep(1);
        log.debug("釋放了鎖");
        lock.unlock();
    }
}

ReentrantLock 默認是不公平的,主要是解決飢餓問題,但是沒有必要設置公平鎖,因爲會降低併發度。

4. 條件變量

在這裏插入圖片描述

@Slf4j(topic = "c.Test24")
public class Test24 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;
    static ReentrantLock ROOM = new ReentrantLock();
    // 等待煙的休息室
    static Condition waitCigaretteSet = ROOM.newCondition();
    // 等外賣的休息室
    static Condition waitTakeoutSet = ROOM.newCondition();

    public static void main(String[] args) {

        new Thread(() -> {
            ROOM.lock();
            try {
                log.debug("有煙沒?[{}]", hasCigarette);
                while (!hasCigarette) {
                    log.debug("沒煙,先歇會!");
                    try {
                        waitCigaretteSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("可以開始幹活了");
            } finally {
                ROOM.unlock();
            }
        }, "小南").start();

        new Thread(() -> {
            ROOM.lock();
            try {
                log.debug("外賣送到沒?[{}]", hasTakeout);
                while (!hasTakeout) {
                    log.debug("沒外賣,先歇會!");
                    try {
                        waitTakeoutSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("可以開始幹活了");
            } finally {
                ROOM.unlock();
            }
        }, "小女").start();

        sleep(1);
        new Thread(() -> {
            ROOM.lock();
            try {
                hasTakeout = true;
                waitTakeoutSet.signal();
            } finally {
                ROOM.unlock();
            }
        }, "送外賣的").start();

        sleep(1);

        new Thread(() -> {
            ROOM.lock();
            try {
                hasCigarette = true;
                waitCigaretteSet.signal();
            } finally {
                ROOM.unlock();
            }
        }, "送煙的").start();
    }
}
10:16:47.228 c.Test24 [小女] - 外賣送到沒?[false]
10:16:47.247 c.Test24 [小女] - 沒外賣,先歇會!
10:16:47.248 c.Test24 [小南] - 有煙沒?[false]
10:16:47.248 c.Test24 [小南] - 沒煙,先歇會!
10:16:48.200 c.Test24 [小女] - 可以開始幹活了
10:16:49.241 c.Test24 [小南] - 可以開始幹活了
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章