文章目錄
一、
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) 的區別:
- sleep 是 Thread 方法,而 wait 是 Object 的方法
- sleep 不需要強制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
- sleep 在睡眠的同時,不會釋放對象鎖的,但 wait 在等待的時候會釋放對象鎖
- 線程狀態相同: 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 [小南] - 可以開始幹活了