wait/notify/notifyAll常见面试题解析

前言
前面几篇文章讲述了多线程的基本知识,今天我们来看几个多线程的面试题。

面试常见问题:

  • 为什么 wait 必须在 synchronized 同步代码中使用?
  • 为什么 wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?
  • wait/notify 和 sleep 方法的异同?

为什么 wait 必须在 synchronized 同步代码中使用?

至于这个问题,其实想一想如果不在synchronized同步代码中使用的话会怎么样,答案就很清楚了。下面通过一个例子来说明这个问题:

class MyQueue {
    private final ArrayList<String> list = new ArrayList<>(5);
	
	public void consume() {
		while(true) {
			if(list.size()==0) {
				 list.wait();
			}
			System.out.println(list.remove(0));
			list.notify();
		}
	}
		
	public void product() {
		while(true) {
			if(list.size()==5) {
				list.wait();
			}
			list.add("element");
			list.notify();
		}
	}
}

在上述伪代码中,类MyQueue中有两个方法,分别提供给两个线程调用,消费者线程调用consume() 方法,生产者线程调用 product() 方法,如果我们不加synchronized关键字加锁的话,下面我们来看一种可能的场景(关于生产者消费者模式可以查看生产者消费者模式详解):

  • 消费者线程在判断if(list.size()==0)的时候成功了,这时候应该调用list.wait()方法来等待其它线程添加元素,但是因为没有添加synchronized,这时候list.wait()方法不一定会执行,可能线程被CPU暂停了。
  • 生产者线程这时候开始执行添加的任务,当添加完元素之后,调用list.notify()的时候想要通知等待的线程,可是因为没有需要等待的线程,因为消费者线程并没有执行wait()方法,所以就可能导致消费者线程出现一直等待的错误。
  • 如果我们对两个代码块都加锁的话,所有的执行就都是原子性的了,就不会出现消费者线程list.size()==0 和 list.wait()中间断开,导致生产者线程的notify()插入其中 的情况了。这样的话程序就安全了。
  • 所以wait 为什么必须在 synchronized 同步代码中使用?就是为了程序的安全性,另外想要调用wait()方法释放锁,也必须先要持有锁对象才可以,否则的话是会报IllegalMonitorStateException异常的。

为什么 wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?

可能我们司空见惯了wait/notify/notifyAll 在Object类中,直接使用它,但是并不关心它为什么这么设置的原因。针对这个问题,其实也好理解,主要原因如下:

  • 因为Java的每一个对象都有一个锁(monitor 或者监视器),而wait/notify/notifyAll的目的是为了等待其它线程释放持有的对象的锁,或者唤醒其它线程持有对象的锁,而且Java线程中也没有任何可供对象使用的锁和同步器,另外将这些方法定义在Object中,也是为了使得Java的每一个对象都可以用于线程间通信。
  • 因为对象中的锁是对象级别的,而非线程级别的,wait/notify/notifyAll也都是针对锁级别的操作,它们的锁属于对象,所以把它们定义在Object类中是最合适,因为Object类是所有对象的父类。
  • 如果把wait/notify/notifyAll 方法定义在 Thread 类中,我们该如何让一个线程持有多把锁?又如何确定线程等待的是哪一把锁?既然是线程等待某一个对象的锁,那么操作对象是最为合适的,而不是线程。

wait 和 sleep 方法的异同?

在Java线程六种状态图解中我们对线程的六种状态做了详细的分析,这里我们再来总结一下。

  • 相同点:都可以使得线程阻塞,并且都可以在阻塞过程中感受到中断信号,抛出InterruptedException 异常。
  • 不同点:
    • 首先从是否释放锁的角度来讲,wait()方法会释放锁,但是sleep()方法会一直持有锁,这就导致wait()/wait(time)方法结束后会将线程从WAITING或者TIME_WAITING状态变为BLOCKED状态,只有再次持有锁线程才会变为RUNNABLE状态,而sleep()方法因为一直持有锁,导致超时之后直接从TIME_WAITING状态变为RUNNABLE状态。
    • wait方法必须在synchronized同步代码中使用,并且会释放锁,而sleep不需要。
    • wait方法是Object的方法,sleep是Thread的方法。
    • sleep方法是必须要求指定一个时间,超时之后就会恢复操作,而wait()方法如果没有设置时间的话, 会一直处于等待状态,只有等到被唤醒或者被中断才会恢复。

你可能感兴趣的:(wait/notify/notifyAll常见面试题解析)