Java多线程之对象等待集

本文详细介绍了Java中对象等待集的概念,以及wait、notify和notifyAll方法在协调多线程执行顺序中的作用。通过ATM取款机的例子,解释了线程等待和唤醒的逻辑,强调了这些方法必须在synchronized块中使用。wait使线程等待并释放锁,notify唤醒单个等待线程,notifyAll唤醒所有等待线程。此外,还对比了wait与sleep方法的区别。
摘要由CSDN通过智能技术生成

对象等待集起到的效果就是,协调多个线程之间执行的先后顺序~

举个例子:一群人去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()内部做了三件事~~

  1. 释放锁(所以需要先有锁,才能释放锁)
  2. 等其它线程的通知
  3. 等通知来了,重新尝试获取锁~

举个例子: 还是张三去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 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞
一段时间,唯一的相同点就是都可以让线程放弃执行一段时间。但还是有一些细微区别:

  1. wait 之前需要请求锁,而wait执行时会先释放锁,等被唤醒时再重新请求锁。这个锁是 wait 对象上的 monitor lock
  2. sleep 是无视锁的存在的,即之前请求的锁不会释放,没有锁也不会请求,会自动醒来。
  3. wait 是 Object 的方法
  4. sleep 是 Thread 的静态方法
  5. wait必须是在synchronized的同步代码块中
  6. sleep可以在任何地方
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值