开始正题之前,我们要先进行一点知识点的补充
上一期我们更新了一期关于线程安全的知识,对于volatile在这里在做出一些补充
有些文章上说线程修改一个变量的时候,从主内存读取到工作内存上,在工作内存上修改完以后再返回主内存
由于t1线程频繁从主内存读取数据,且值一样,所以编译器做了优化,让t1读取工作内存,
t2修改主内存的值,t1只读工作内存,所以修改无效
这里的主内存指的是内存,工作内存指的是CPU寄存器+缓存
在Java官方文档中使用主内存和工作内存这样的语言
因为Java的特点就是跨平台语言,兼容多种操作系统,兼容多种硬件设备,特别是CPU
CPU的种类有很多,现在的CPU上面还有缓存.缓存是CPU内部存储数据的空间
现在市面上的CPU有L1,L2,L3三级缓存
读取缓存的速度介于寄存器和内存之间
读取寄存器的速度比内存快3-4个数量级
当CPU想要读取一个内存数据的时候,过程是这样的
1.看看CPU寄存器有吗
2没有,看L1有吗
3没有,看L2有吗
4没有,看L3有吗
5还没有,看内存有吗
当这个流程中的某一个结构读到了数据,就结束读取
OK,言归正传,上面的知识就是解释了一下主内存和工作内存
下面开始今天的正题
目录
🚀1.wait和notify的介绍
🚀2.代码举例
🚀3.wait和sleep的区别及联系
1.wait和notify的介绍
多线程的调度是随机的,但是在某些情况下我们也希望代码有序,之前学过的join保证了有序,
今天我们就再来学习一个方法,也能保证代码的执行顺序
wait就是让某个线程停下来先等一等,notify就是唤醒这个线程,继续执行
我们举一个例子
举一个取钱的例子
ATM机上取钱的例子
现在1号滑稽要取钱,但是机子里没有钱,他就只能出来,然后过了一会,1号滑稽又进去了,他这样频繁的进出,让其他线程没有机会上CPU调度,这一现象称为线程饿死
为了解决这个局面,我们让1号滑稽老铁先阻塞等待一会,让2,3,4去执行
这个时候我们使用wait,进行阻塞等待,此时,当银行工作人员来了以后,充了钱,这时让1号滑稽去进行取钱操作,这一过程要用到notify,进行唤醒wait的操作
2.代码举例
现在写一个具体的代码来分析分析
1
public class ThreadDemo15 { public static void main(String[] args) throws InterruptedException { Object object = new Object(); System.out.println("wait 之前"); synchronized (object) { object.wait(); } System.out.println("wait 之后"); } }
可以看到运行结果不符合预期,这是因为没有唤醒,所以就一直等待了,我们再写一个带唤醒操作版本的
public class ThreadDemo16 { public static void main(String[] args) throws InterruptedException { Object locker = new Object(); Thread t1 = new Thread(() -> { try { System.out.println("wait 开始"); synchronized (locker) { locker.wait(); } System.out.println("wait 结束"); } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); Thread.sleep(1000); Thread t2 = new Thread(() -> { synchronized (locker) { System.out.println("notify 开始"); locker.notify(); System.out.println("notify 结束"); } }); t2.start(); } }
t1线程在打印完wait开始后就进入了阻塞等待,然后t2开始执行,执行到了notify就要唤醒t1线程,但是由于notify操作在锁里,所以只有当t2线程释放锁以后,t1才可以开始执行
所以执行结果是
注意,wait和notify操作都必须在synchronized操作下执行
且wait和notify以及synchronized的操作对象必须是同一个
wait必须先执行,才能执行notify,如果先执行notify,那么唤醒无效,唤醒了个寂寞啊
wait主要功能
1.解锁
2.阻塞等待(此时t1的锁给了t2)
3.收到通知,唤醒线程,重新获取锁 (t2释放了锁)
wait必须写到synchronized里面
这个代码执行顺序,是t1先执行,但是我们通过wait和notify操作,控制了执行顺序
1.t1先执行一会
2.然后让t2执行一会
3.再让t1执行
唤醒操作
notify方法只是唤醒某一个等待线程.
使用notifyAll方法可以一次唤醒所有的等待线程.
如果t1,t2,t3等待的都是同一个对象,那么在主函数中唤醒,就只是唤醒其中一个,如果调用notifyAll,那么三个线程都会唤醒,然后抢占锁,依次执行
3.下面来想一想wait和sleep的区别和联系
联系
1.wait有个带参数的版本,用来体现超时时间,和sleep差不多,sleep也是有时间参数的
2.他们两个都能提前唤醒,sleep通过interrupt唤醒,而wait通过notify唤醒
区别
初心不同
wait:为了控制代码执行顺序
sleep:单纯让线程休眠一会
实现方式不同
wait要在加锁条件下使用,sleep没有限制条件
wait被调用后当前线程进入waiting状态并释放锁,并可以通过notify和notifyAll方法进行唤醒;sleep被调用后当前线程进入TIMED_WAIT状态,不涉及锁相关的操作;
起源不同:
wait是Object类中的一个方法,sleep是Thread类中的一个方法;
讲到现在,wait也就差不多介绍到这里
今天的内容就讲到这里,我们下期再见!!!