对象等待集起到的效果就是,协调多个线程之间执行的先后顺序~
举个例子:一群人去ATM取款机里去取款,第一个是张三进去去钱,张三进去后,取完钱出来,发现钱取的不够,于是又进去再取一次,这次再出来的时候,又发现钱取多了,再次进去存一点,这样反反复复,就会导致后面排队的人一直用不到取款机。
由于多线程直接是“抢占式”执行,所以往往某一个线程反复的执行,就会导致后面排队的线程,迟迟没有机会去CPU上执行,就会导致线程饿死~
为了解决这个问题,我们借助对象等待集,合理的协调多个线程之间执行的先后顺序~
- wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)
- notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
- 3.wait(long timeout)让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
都是Object类中的方法,并且这些方法必须搭配synchronized使用~,调用这些方法时都是要针对同一个对象进行。
wait()方法
一旦调用 wait方法,这个线程就会阻塞等待,一直等到其他线程来唤醒这个线程。
public class TestWait {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
synchronized (object){
System.out.println("等待中");
object.wait();
System.out.println("等待结束");
}
}
}
此时线程进入WAITTING 状态,上述代码中没有线程去唤醒wait,所以线程一直处于等待状态。
public class TestWait {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
synchronized (object){
System.out.println("等待中");
object.wait(3000);
System.out.println("等待结束");
}
}
}
如果wait()方法中加入具体的等待时间,即使没有线程来唤醒它,当它等待到对应的时间后也会结束等待。
如果wait()方法不在synchronized中使用,就会产生异常:
public class TestWait {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
System.out.println("等待中");
object.wait(3000);
System.out.println("等待结束");
}
}
非法的监视器状态异常:Monito指的是监视器锁,也就是synchronized~,也就是当前预期是获取到锁的状态才能调用wait(),如果没写synchronized相当于还没获取到锁,就尝试调用,就会出现问题 ~
wait()内部做了三件事~~
- 释放锁(所以需要先有锁,才能释放锁)
- 等其它线程的通知
- 等通知来了,重新尝试获取锁~
举个例子: 还是张三去ATM里取钱,当他进去后,发现ATM里面没钱了,然后他就只能出来,在外面等,直到运钞车来了,把钱放进去了,给张三一个信号,有钱了,然后张三再去取钱。
如果张三进去发现没钱,但是他也不出来,那么运钞车来了,也没办法把钱放进去,所以要先释放锁,钱放完了,再去通知有钱了,可以取了。然后排队的人再去竞争,谁先去取钱~~
notify()方法
通知某个线程被唤醒~~ 从wait中醒来 ~
notify也是要放在synchronized中使用,调用notify方法之后,并不会立即释放锁,而是执行完当前的synchronized之后才释放锁,同时等待中的线程就尝试重新竞争这个锁~
public class ThreadDemo {
//创建一个锁对象
static public Object locker = new Object();
//用来等待的线程
static class WaitTask implements Runnable{
@Override
public void run() {
synchronized (locker){
while (true){
try {
System.out.println("wait() 开始!");
locker.wait();
System.out.println("wait() 结束!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//用来通知的线程
static class NotifyTask implements Runnable{
@Override
public void run() {
synchronized (locker){
System.out.println("notify() 开始了!!");
locker.notify();
System.out.println("notify() 结束了!!");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new WaitTask());
Thread t2 = new Thread(new NotifyTask());
t1.start();
Thread.sleep(5000);
t2.start();
}
}
1.WaitTask先进行运行,进入wait方法中,wait方法会释放锁,并等待通知;
2.5秒过后,开始执行NotifyTask;
3.执行了notify方法之后,唤醒了WaitTask线程,WaitTask就从WAITING状态醒来,尝试竞争锁,由于当前锁还没被NotifyTask释放,于是竞争失败,进入阻塞队列,进入BLOCKED状态;
4.NotifyTask执行完后,锁被释放,WaitTask才能竞争到锁,于是从wait内部返回,然后打印“wait结束”;
5.继续进入下一次循环在进行等待~
notifyAll()方法
以上讲了notify方法只是唤醒某一个等待线程,那么如果有多个线程都在等待中怎么办呢,这个时候
就可以使用notifyAll方法可以一次唤醒所有的等待线程。
public class ThreadDemo {
//创建一个锁对象
static public Object locker = new Object();
//用来等待的线程
static class WaitTask implements Runnable{
@Override
public void run() {
synchronized (locker){
while (true){
try {
System.out.println("wait1() 开始!");
locker.wait();
System.out.println("wait1() 结束!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class Wait implements Runnable{
@Override
public void run() {
synchronized (locker){
while (true){
try {
System.out.println("wait2() 开始!");
locker.wait();
System.out.println("wait2() 结束!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//用来通知的线程
static class NotifyTask implements Runnable{
@Override
public void run() {
synchronized (locker){
System.out.println("notifyAll() 开始了!!");
locker.notifyAll();
System.out.println("notifyAll() 结束了!!");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new WaitTask());
Thread t2 = new Thread(new NotifyTask());
Thread t3 = new Thread(new Wait());
t1.start();
t3.start();
Thread.sleep(5000);
t2.start();
}
}
当WAITING状态的对列中有多个线程等待时,调用notifyAll方法,将等待中的对列全部唤醒,但是由于唤醒后还要再次获取锁,并不是所有的线程都能进入就绪队列,而是获取到锁的进入就绪队列,没获取到锁的线程,进入到阻塞队列中。
3个线程都尝试去获取锁,但是只有一个线程获取到锁,没获取到锁的线程就进入到阻塞队列,等待线程执行完,在重新去获取锁~
拓展:
wait和sleep的区别
其实理论上 wait 和 sleep 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞
一段时间,唯一的相同点就是都可以让线程放弃执行一段时间。但还是有一些细微区别:
- wait 之前需要请求锁,而wait执行时会先释放锁,等被唤醒时再重新请求锁。这个锁是 wait 对象上的 monitor lock
- sleep 是无视锁的存在的,即之前请求的锁不会释放,没有锁也不会请求,会自动醒来。
- wait 是 Object 的方法
- sleep 是 Thread 的静态方法
- wait必须是在synchronized的同步代码块中
- sleep可以在任何地方