目录
wait / notify
wait / notify
等待/通知
协调线程之间的执行逻辑的顺序的
可以让后执行的逻辑等待先执行的逻辑
虽然无法直接干预调度器的调度顺序
但是可以让后执行的逻辑(线程)等待,等待到先执行的逻辑跑完了
通知一下当前线程让他执行
join也是等
jion是等另一个线程彻底执行完,才继续走
wait是等到另一个线程执行notify,才继续走(不需要另一个线程也执行完)
锁的等待是不受控制的
某个线程的代码执行到加锁的逻辑不一定触发等待
不确定其他线程是否是“加锁”状态
当多个线程竞争到一把锁的时候
获取到锁的线程如果释放了,
因为随机调度不确定其他哪个线程拿到锁
操作系统的调度是随机的
其他线程的调度都属于在锁上阻塞等待,是阻塞状态
当前这个释放锁的线程,是就绪状态
这个线程有很大的概论能够再次拿到这个锁
这样的行为称之为“线程饿死”
把每个线程想象成鸟宝宝
cpu就是 鸟妈妈
鸟妈妈喂虫给鸟宝宝吃
一直捞不着cpu去执行就饿死了
当拿到锁的线程发现要执行任务的时候,时机不够成熟就使用wait阻塞等待
wait和notify都是Object的方法
Java中的任意对象都提供了wait和notify
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
synchronized (object) {
System.out.println("等待中");
object.wait();
System.out.println("等待结束");
}
}
Object.wait();第一件事情,就是先释放Object对象对应的锁
能够释放锁的前提是,Object对象应该处于加锁状态才能释放
得先加上锁,才能谈释放
为啥wait要先释放锁呢
wait这个等待,最关键的一点,要先释放锁,给其他线程获取锁的机会
synchronized(object) {//加锁操作
object.wait();
//代码进入wait,就会先释放锁,并且阻塞等待
//如果其他线程做完了必要的工作,调用notify唤醒这个wait线程
//wait就会解除阻塞,重新获取到锁,继续执行并返回
}
要求synchronized的锁对象必须和wait的对象是同一个
scanner.next();等待io进入的阻塞
此处的next就是一个带有阻塞的操作
等待用户在控制台输入
locker.notify();
这里同样也是需要先拿到锁,再进行notify(属于是java中给出的限制)
wait操作必须要搭配锁来进行,wait会先释放锁
notify操作,原则上来说,不涉及加锁解锁操作
在java中,也强制要求notify搭配synchronized
这4处必须是相同的对象
wait和notify是针对同一个对象,才能生效
这个相同的对象,这两线程沟通的桥梁
如果是两个不同的对象则没有任何相互的影响和作用
必须要确保,notify的执行要在wait之后
先wait后notify才有作用
如果先notify,后wait此时wait无法被唤醒
notify的这个线程也没有副作用(notify一个没有wait的对象,不会报错)
没有副作用指的是,线程自身没有抛出异常/其他报错
如果有多个线程在同一个对象上wait
进行notify的时候是随机唤醒其中的一个线程
一次notify唤醒一个wait
notifyAll一次唤醒所有wait线程
synchronized (locker) {
locker.notifyAll();
}
虽然同时唤醒了t1和t2
由于wait唤醒之后要重新加锁
其中某个线程先加上锁,开始执行
另一个线程因为加锁失败再次阻塞等待
等待先走的线程解锁了,后走的线程才能加上锁继续执行
wait和join类似
也是提供了“死等”版本和“超时时间”版本
locker.wait(1000);
wait引入超时时间之后直观看起来和sleep很像
wait有等待时间
sleep也有等待时间
wait可以使用notify提前唤醒
sleep可以使用interrupt提前唤醒
wait和sleep最主要的区别在于针对锁的操作
1.wait必须要搭配锁,先加锁,才能用wait,sleep不需要
2.如果都是在synchronized内部使用,wait会释放锁,sleep不会释放锁
synchronized(locker){
Thread.sleep(1000);
}
抱着锁睡
其他线程也是没法获取到这个锁的
Interrupt其实本身的作用是通知线程终止
3.wait是Object的方法sleep是Thread的静态方法