java EE初阶 — wait 和 notify

1.wait 和 notify

线程最大的问题是抢占式指向,随机调度。而写代码的时候,确定的东西会比较好。

于是就有程序猿发明了一些办法,来控制线程之间的执行顺序。
虽然线程在内核里的调度是随机的,但是可以通过一些 API 然线程主动阻塞,主动放弃 CPU。(给别的线程让路)

比如,t1 t2 两个线程,希望 t1 先干活,干的差不多的时候,再让 t2 来干。
就可以让 t2 wait 。(阻塞,主动放弃 CPU)

上面的场景,使用 joinsleep 无法做到。
使用 jion 则必须要 t1 彻底执行完毕,t2 才可以运行。
如果是希望 t1 先干50%的活,就让 t2 开始行动,join 无能为力。
使用 sleep 指定一个休眠时间,但是 t1 执行的这些活,到底花了多少的时间,不好估计。

于是就可以使用 waitnotify

waitnotifynotifyAll 这几个类,都是,Object类中的方法。

1.1 wait()方法

wait 的作用是进行阻塞。

当某个线程调用 wait 方法时,就会进入阻塞(无论是通过哪个对象 wait 的)。
此时的状态就处在 WAITING


先来看一段代码。

package thread;

public class ThreadDemo18 {
    public static void main(String[] args) throws InterruptedException{
        Object object = new Object();
            object.wait();
        }
    }
}

throws InterruptedException
有很多带阻塞功能的方法都有这个异常。
这些方法都是可以被 interrupt 方法通过这个异常唤醒。

wait 不加任何参数就会一直等待,直到有其他的线程唤醒它。

上面的代码会抛一个异常。


这是一个非法的锁状态异常

锁的状态:被加锁的状态和解锁的状态。

wait 的操作分为:

  1. 先释放锁。
  2. 进行阻塞等待
  3. 收到通知之后,重新尝试获取锁,并且在获取锁之后继续往下执行。

这里的锁的状态异常,就是没加锁,就要要释放锁了。

解决办法就是搭配 synchronized 来使用。

package thread;

public class ThreadDemo18 {
    public static void main(String[] args) throws InterruptedException{
        Object object = new Object();
        synchronized (object) {
            System.out.println("wait之前");
            object.wait();
            System.out.println("wait之后");
        }

    }
}



虽然这里的 wait 是阻塞在 synchronized 代码块里了,
但是实际上,这里的阻塞是释放了锁的。
此时其他线程是可以获取到 object 这个对象的锁的。

此时这里的阻塞就处于 WAITING

1.2 notify()方法

notify 方法是唤醒等待的线程。

来看一段代码

package thread;

public class ThreadDemo19 {
    public static void main(String[] args) {
        Object object = new Object();
        Thread t1 = new Thread(() -> {
            //线程1负责进行等待
            System.out.println("t1 wait之前");
            try {
                synchronized (object) {
                    object.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1 wait之后");
        });

        Thread t2 = new Thread(() -> {
            //线程2负责唤醒
            System.out.println("t2 notify之前");
                synchronized (object) {
                    object.notify();
                }
            System.out.println("t2 notify之后");
        });
        t1.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}


此处,先执行了 wait 很明显 操作阻塞了,没有看到 wait 之后的打印。
接下来执行到了 t2 ,t2 进行了 notify 的时候,才会把 t1 的 wait 唤醒,t1 才能继续执行。

此处的通知和 wait 配对。
如果 wait 使用的对象和 notify 使用的对象不同。
此时的 notify 不会有任何效果。(notify 只能唤醒在同一个对象上等待的线程)

如果的代码这里写作:t1.startt2.start
由于线程不确定性,此时不能保证是先执行 wait,后执行 notify

如果调用 notify 此时没有 wait 。此处的 wait 是无法被唤醒的。
因此此处的代码要求保证先执行 wait 后执行 notify,这样才是有意的。


wait 和 sleep 的区别
wait 的带有时间的版本看起来就和 sleep 有点像。
但是其实还是有区别的,虽然都是可以指定等待的时间,也都能被提前唤醒,
(wait 是被 nottify 唤醒,sleep 是被 interrupt 唤醒)
但是这里表示的含义截然不同。

  • notify 唤醒 wait 是不会有任何异常的。(正常的业务逻辑)
  • interr 唤醒 sleep 则是会出现异常。(表示一个出问题的逻辑)


下面看一道练习:

有三个线程,输出字母 ABC,控制三个线程分别按照 ABC 的顺序打印出来。

package thread;

public class ThreadDemo20 {
    //有三个线程,控制三个线程分别按照 ABC 的顺序打印出来
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println('A');
        });

        Thread t2 = new Thread(() -> {
            System.out.println('B');
        });

        Thread t3 = new Thread(() -> {
            System.out.println('C');
        });

        t1.start();
        t2.start();
        t3.start();
    }
}

因为它的结果是随机调度的因此不能确定先输出哪个,结果可能是 ABC,也可能是ACB,或者是其他的结果。



我们需要通过 wait 和 notify 告知 线程1 在打印完A之后通知线程2可以打印B了
要保证线程2要阻塞到A打印结束,才会开始打印B。
线程3也是同理,直到按照顺序打印结束是会出现 ABC。


下面是优化过的版本。

package thread;

public class ThreadDemo20 {
    //有三个线程,控制三个线程分别按照 ABC 的顺序打印出来
    public static void main(String[] args) throws InterruptedException{
        Object locker1 = new Object();
        Object locker2 = new Object();

        Thread t1 = new Thread(() -> {
            System.out.println('A');
            synchronized (locker1) {
                //告知线程2B可以打印了
                locker1.notify();
            }
        });

        Thread t2 = new Thread(() -> {
            //等待线程1打印完A再打印B
            synchronized (locker1) {
                try {
                    locker1.wait();//等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println('B');
            synchronized (locker2) {
                //告知线程3C可以打印了
                locker2.notify();
            }
        });

        Thread t3 = new Thread(() -> {
            synchronized (locker2) {
                try {
                    locker2.wait();//等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println('C');
        });

        t1.start();
        t2.start();
        t3.start();
    }
}

通过 wait 和 notify 来实现三个线程之间相互等待和通知的作用。


c此时就是预期好的顺序了。

但是这样还是会有一些小问题:

如果是先执行线程2的 notify 后执行线程2的 wait ,此时程序就会僵住

解决办法就是 保证线程1的启动速度要慢于线程2和线程3

关键代码如下:

 t2.start();
 t3.start();
 Thread.sleep(1000);
 t1.start();

调整完线程1的顺序后,再加一个睡眠时间 1000 ms。

1.3 notifyAll()方法

notify 只能唤醒某一个等待线程,而 notifyAll 可以唤醒所有的等待线程。


注意:

虽然是同时唤醒多个线程, 但是这多个线程都需要竞争锁. 所以并不是同时执行, 而仍然是有先有后的执行。


这是 notify 只能唤醒一个。


这是 notifyAll 可以换线多个线程,但是多个线程需要竞争一把锁。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

与大师约会

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值