由于线程之间是 抢占式执行的,因此 线程之间执行的先后顺序难以预知. 但是实际开发中有时候我们希望 合理的协调多个线程之间的执行先后顺序。join()方法也可以一定程度控制线程执行的顺序,但是 join()只能,而 wait()方法就能的控制线程的顺序,让线程之间更好的协作,从而完成需求。

代码演示

我们先看一段示例代码

package waitAndnotify;

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

运行结果~~

wait 之前
Exception in thread "main" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at waitAndnotify.TestDemo.main(TestDemo.java:7)

Process finished with exit code 1

当我们执行时,好像并没有达到我们预期的等待效果,而是报了一个IllegalMonitorStateException非法的监视器状态异常,也就是非法的异常.

要想了解这里为什么报错,我们就要清楚wait的执行过程和特性

注意: wait, notify, notifyAll 都是 Object 类的方法.

wait() - 阻塞等待

wait()方法内部作了什么工作:

  1. 当前的锁.
  2. 让线程进入.
  3. 满足一定条件时被.
  4. .

wait() 结束等待的条件:

  1. 其他线程调用该对象的 方法. .
  2. wait 等待 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间). .
  3. 其他线程调用该等待线程的 方法, 导致 wait 抛出 InterruptedException 异常…

到此,我们就知道了示例代码为什么会抛出IllegalMonitorStateException异常,我们将代码进行调整:

package waitAndnotify;

// wait - > Object 类的对象
public class Demo01_wait {
    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        synchronized (o) {
            System.out.println("wait 之前");
            // 将wait放到synchronized中,保证以及加锁
            o.wait();
            System.out.println("wait 之后");
        }
    }
}

notify() - 通知唤醒

notify 方法是唤醒等待()的线程.

  • 方法notify()也要在同步方法或同步块,该方法是用来通知那些可能等待该对象的对象锁。其它线程,对其发出通知notify,并使它们。
  • 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”,)
  • 在notify()方法后,当前线程,要等到执行notify()方法的线程将程序执行完,也就是。

    我们通过代码来观察一下wait()和notify()的执行。

public class waitDemo {
    public static void main(String[] args) {
        Object object = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (object) {
                System.out.println("wait 之前");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("wait 之后");
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (object) {
                System.out.println("进行通知");
                object.notify();
            }
        });

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

执行结果~~

wait 之前
进行通知
wait 之后

进程已结束,退出代码为 0

在这个代码中,t1 线程启动后,输出“wait之前,然后加锁并且遇到wait()方法,使t1线程进入阻塞状态并且释放锁,t2线程在水面3000ms后,由于t1线程已经释放锁,t2线程就可以拿到锁从而执行到notify()方法唤醒t1线程,输出“进行通知”,此时释放锁,t1线程重新拿到锁,才可以执行“wait()之后”,从被通知到唤醒是有一个时间的,不是通知之后立即唤醒。

  1. t2线程释放锁对象
  2. t1线程再次被CPU调度执行

当我们在多个线程中加入锁之后,由于这些线程是抢占式并发执行的,这些线程就会去竞争这把锁,当某一个线程竞争到锁之后,如果由于缺乏某些条件导致CPU没有执行该线程,然后该线程释放锁之后还会继续去参与竞争。如果极端情况下一直都是该线程抢到锁,,就像没有抢到食物被饿死了一样,这就叫做“线程饿死”现象。

使用wait()就可以让这个缺乏条件的线程进入Waiting状态(阻塞),从而避免不满足条件的该线程继续竞争,当满足条件是,在合适的时机唤醒该线程即可。

notifyAll() - 全部唤醒

notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程. notifyAll()虽然是全部唤醒,但是这个过程也是一个一个(串行)线程的唤醒的,因为要唤醒线程之前要先拿到锁对象。

wait和notify要借助同一个对象

wait 和 sleep 的比较

wait()用于线程之间的通信;sleep()让线程阻塞一段时间,唯一的相同点就是都可以让线程放弃执行一段时间。

  1. wait 需要搭配 synchronized 使用 sleep() 不需要
  2. sleep是Thread类的静态方法,wait()是Object类的成员方法
  3. wait被notify()唤醒是通过线程间通信,不会报异常,sleep被中途唤醒会报异常
11-25 07:56