JUC共享模型下
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC共享模型下相关的知识,希望对你有一定的参考价值。
JUC共享模型下
wait notify
小故事 - 为什么需要 wait
由于条件不满足,小南不能继续进行计算
但小南如果一直占用着锁,其它人就得一直阻塞,效率太低
于是老王单开了一间休息室(调用 wait 方法),让小南到休息室(WaitSet)等着去了,但这时锁释放开,其它人可以由老王随机安排进屋
直到小M将烟送来,大叫一声 [ 你的烟到了 ] (调用 notify 方法)
小南于是可以离开休息室,重新进入竞争锁的队列
wait notify 原理
- Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
- BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片
- BLOCKED 线程会在 Owner 线程释放锁时唤醒
- WAITING 线程会在 Owner 线程调用 notify 或 notifyAll时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争
- 如果同时有两个WAITING 的线程,当他们被唤醒时,先进入EntryList 重新竞争锁,如果竞争失败,没有获取到锁的那个线程,会再次进入等待状态
API 介绍
- obj.wait() 让进入 object 监视器的线程到 waitSet 等待
- obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒
- obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒
它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法
notify和notifyall方法执行的前提也都是对象获得了锁
@Slf4j
public class Main
final static Object obj = new Object();
public static void main(String[] args) throws InterruptedException
new Thread(() ->
synchronized (obj)
log.debug("执行....");
try
obj.wait(); // 让线程在obj上一直等待下去
catch (InterruptedException e)
e.printStackTrace();
log.debug("其它代码....");
).start();
new Thread(() ->
synchronized (obj)
log.debug("执行....");
try
obj.wait(); // 让线程在obj上一直等待下去
catch (InterruptedException e)
e.printStackTrace();
log.debug("其它代码....");
).start();
// 主线程两秒后执行
sleep(2);
log.debug("唤醒 obj 上其它线程");
synchronized (obj)
obj.notify(); // 唤醒obj上一个线程
// obj.notifyAll(); // 唤醒obj上所有等待线程
notify 的一种结果
20:00:53.096 [Thread-0] c.TestWaitNotify - 执行....
20:00:53.099 [Thread-1] c.TestWaitNotify - 执行....
20:00:55.096 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
20:00:55.096 [Thread-0] c.TestWaitNotify - 其它代码....
notifyAll 的结果
19:58:15.457 [Thread-0] c.TestWaitNotify - 执行....
19:58:15.460 [Thread-1] c.TestWaitNotify - 执行....
19:58:17.456 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
19:58:17.456 [Thread-1] c.TestWaitNotify - 其它代码....
19:58:17.456 [Thread-0] c.TestWaitNotify - 其它代码....
- wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到 notify为止
- wait(long n) 有时限的等待, 到 n 毫秒后结束等待,或是被 notify
wait notify 的正确姿势
sleep(long n) 和 wait(long n) 的区别
- 1) sleep 是 Thread 方法,而 wait 是 Object 的方法
- 2) sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
- 3) sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
- 4) 它们都是状态 TIMED_WAITING
使用案例
思考下面的解决方案好不好,为什么?
import lombok.extern.slf4j.Slf4j;
import static java.lang.Thread.sleep;
@Slf4j
public class Main
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
sleep(2);
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();
try
//抛出可能被其他线程打断的异常
sleep(1);
catch (InterruptedException e)
e.printStackTrace();
new Thread(() ->
// 这里能不能加 synchronized (room)?
hasCigarette = true;
log.debug("烟到了噢!");
, "送烟的").start();
输出
20:49:49.883 [小南] c.TestCorrectPosture - 有烟没?[false]
20:49:49.887 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:49:50.882 [送烟的] c.TestCorrectPosture - 烟到了噢!
20:49:51.887 [小南] c.TestCorrectPosture - 有烟没?[true]
20:49:51.887 [小南] c.TestCorrectPosture - 可以开始干活了
20:49:51.887 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.887 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
- 其它干活的线程,都要一直阻塞,效率太低
- 小南线程必须睡足 2s 后才能醒来,就算烟提前送到,也无法立刻醒来
- 加了 synchronized (room) 后,就好比小南在里面反锁了门睡觉,烟根本没法送进门,main 没加
synchronized 就好像 main 线程是翻窗户进来的 - 解决方法,使用 wait - notify 机制
案例优化1
import lombok.extern.slf4j.Slf4j;
import static java.lang.Thread.sleep;
@Slf4j
public class Main
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(2000);
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();
try
sleep(1);
catch (InterruptedException e)
e.printStackTrace();
new Thread(() ->
synchronized (room)
hasCigarette = true;
log.debug("烟到了噢!");
room.notify();
, "送烟的").start();
输出
20:51:42.489 [小南] c.TestCorrectPosture - 有烟没?[false]
20:51:42.493 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:51:42.493 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.493 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:43.490 [送烟的] c.TestCorrectPosture - 烟到了噢!
20:51:43.490 [小南] c.TestCorrectPosture - 有烟没?[true]
20:51:43.490 [小南] c.TestCorrectPosture - 可以开始干活了
问题:
- 解决了其它干活的线程阻塞的问题,但如果有其它线程也在等待条件呢?
- 因为notify会随机唤醒一个沉睡线程,如果出现唤醒不希望唤醒的线程情况怎么办
虚假唤醒问题演示
import lombok.extern.slf4j.Slf4j;
import static java.lang.Thread.sleep;
@Slf4j
public class Main
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("可以开始干活了");
<以上是关于JUC共享模型下的主要内容,如果未能解决你的问题,请参考以下文章