1、介绍
在Java多线程编程中,一个线程可能要等待其他线程的某些操作,才能继续执行。如果使用wait和notify进行线程间通信,则可以实现线程的等待和唤醒。wait方法使当前线程等待,而notify方法则可以随机唤醒一个等待该对象的线程。但是,使用notify可能会产生虚假唤醒(spurious wakeup),即线程在未被notify的情况下,也会被唤醒。此时,就要使用notifyAll,它会唤醒所有等待该对象的线程。
2、正文
一、notify和notifyAll的区别
notifyAll方法会唤醒所有等待该对象的线程,而notify只会唤醒其中一个线程。如果唤醒的线程不是想要的线程,则需要再次等待,而这会浪费时间和资源。因此,在多线程编程中,应该尽可能使用notifyAll方法。
二、notifyAll的使用方法
在使用notifyAll方法时,需要注意以下几个方面:
1、使用notifyAll方法需要获取对象的锁
在调用notifyAll方法之前,必须先获取对象的锁,否则会抛出IllegalMonitorStateException异常。例如:
synchronized(object){ // ... object.notifyAll(); }
2、notifyAll方法唤醒的线程需要竞争锁
当线程通过notifyAll方法醒来时,需要竞争对象的锁才能继续执行。因此,在唤醒线程之前,应该先释放对象的锁。例如:
synchronized(object){ // ... object.notifyAll(); } // 当前线程释放锁
3、wait和notifyAll方法要在synchronized代码块中使用
wait和notifyAll方法需要在synchronized代码块中使用,因为要获取对象的锁。如果不在synchronized代码块中使用,会抛出IllegalMonitorStateException异常。例如:
synchronized(object){ // ... object.wait(); }
三、notifyAll的实现原理
使用wait和notifyAll方法时,操作系统会对每个对象(object)维护一个wait set和一个entry set。
wait set中包含了所有等待该对象的线程,而entry set则包含了该对象正在占用的线程。当一个线程调用对象的wait方法时,它会被加入到该对象的wait set中,并释放对象的锁。当另一个线程调用对象的notify或notifyAll方法时,它会从该对象的wait set中选择一个等待时间最长的线程,将其从wait set中移除,并加入到entry set中,从而使其竞争对象的锁。
使用notify方法时,操作系统会随机选择一个等待该对象的线程,将其从wait set中移到entry set中。但是,在某些情况下,会出现虚假唤醒的情况。例如,操作系统可能会在不经意的时候唤醒所有等待该对象的线程,而这些线程并没有收到notify的通知。这种情况下,notifyAll方法就能够避免虚假唤醒的问题,因为它会唤醒所有等待该对象的线程。
3、代码示例
下面是一个使用notifyAll方法的示例代码:
public class WaitNotify { public static void main(String[] args) { final Object lock = new Object(); Thread t1 = new Thread(() -> { synchronized (lock) { try { System.out.println("Thread 1 starts waiting"); lock.wait(); System.out.println("Thread 1 is awakened"); } catch(InterruptedException e) { e.printStackTrace(); } } }); Thread t2 = new Thread(() -> { synchronized (lock) { try { System.out.println("Thread 2 starts waiting"); lock.wait(); System.out.println("Thread 2 is awakened"); } catch(InterruptedException e) { e.printStackTrace(); } } }); Thread t3 = new Thread(() -> { synchronized (lock) { System.out.println("Thread 3 starts notifying"); lock.notifyAll(); } }); t1.start(); t2.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } t3.start(); } }
运行结果:
Thread 1 starts waiting Thread 2 starts waiting Thread 3 starts notifying Thread 2 is awakened Thread 1 is awakened
总结
在Java多线程编程中,notifyAll方法是实现线程间通信的重要方式之一。在使用notifyAll方法时,需要注意锁的获取和释放、wait和notifyAll方法的使用等方面,以避免出现问题。同时,notifyAll方法在遇到虚假唤醒问题时也能够提供一种有效的解决方案。