Java 使用 wait / notify / notifyAll 实现线程间通信

在 Java 中,可以通过配合调用 Object 对象的 wait() 方法和 notify() 方法或 notifyAll() 方法来实现线程间的通信。在线程中调用 wait() 方法,将阻塞等待其他线程的通知(其他线程调用 notify() 方法或 notifyAll() 方法),在线程中调用 notify() 方法或 notifyAll() 方法,将通知其他线程从 wait() 方法处返回。

Object 是所有类的超类,它有 5 个方法组成了等待 / 通知机制的核心:notify()、notifyAll()、wait()、wait(long) 和 wait(long, int)。在 Java 中,所有的类都从 Object 继承而来,因此,所有的类都拥有这些共有方法可供使用。而且,由于他们都被声明为 final,因此在子类中不能覆写任何一个方法。

wait()

public final void wait() throws InterruptedException, IllegalMonitorStateException

该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用 wait() 之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用 wait() 方法。进入 wait() 方法后,当前线程释放锁。如果调用 wait() 时,没有持有适当的锁,则抛出 IllegalMonitorStateException,它是 RuntimeException 的一个子类,因此,不需要 try-catch 结构。线程被唤醒后,会从上次调用 wait() 方法的地方继续执行。所以一般在循环中调用 wait() 方法,对于从 wait() 中被 notify() 的进程来说,它在被 notify() 之后还需要重新检查是否符合执行条件,如果不符合,就必须再次被 wait(),如果符合才能往下执行

notify()

public final native void notify() throws IllegalMonitorStateException

该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,的如果调用 notify() 时没有持有适当的锁,也会抛出 IllegalMonitorStateException。

该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个 wait() 状态的线程来发出通知,并使它等待获取该对象的对象锁(notify 后,当前线程不会马上释放该对象锁,wait 所在的线程并不能马上获取该对象锁,要等到程序退出 synchronized 代码块后,当前线程才会释放锁,wait 所在的线程也才可以获取该对象锁),但不惊动其他同样在等待被该对象 notify 的线程们。当第一个获得了该对象锁的 wait 线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用 notify 语句,则即便该对象已经空闲,其他 wait 状态等待的线程由于没有得到该对象的通知,会继续阻塞在 wait 状态,直到这个对象发出一个 notify 或 notifyAll。这里需要注意:它们等待的是被 notify 或 notifyAll,而不是锁。这与下面的 notifyAll() 方法执行后的情况不同。

notifyAll()

public final native void notifyAll() throws IllegalMonitorStateException

该方法与 notify() 方法的工作方式相同,重要的一点差异是:

notifyAll 使所有原来在该对象上 wait 的线程统统退出 wait 的状态(即全部被唤醒,不再等待 notify 或notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll 线程退出调用了 notifyAll 的 synchronized 代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出 synchronized 代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

wait(long) 和 wait(long, int)

这两个方法是设置等待超时时间的,后者在超时时间上加上 ns,精度也难以达到,因此,该方法很少使用。对于前者,如果在等待线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争重新获得锁,并从 wait(long) 返回。另外,需要知道,如果设置了超时时间,当 wait() 返回时,我们不能确定它是因为接到了通知还是因为超时而返回的,因为 wait() 方法不会返回任何相关的信息。但一般可以通过设置标志位来判断,在 notify 之前改变标志位的值,在 wait() 方法后读取该标志位的值来判断,当然为了保证 notify 不被遗漏,我们还需要另外一个标志位来循环判断是否调用 wait() 方法。

生产者消费者实例说明 wait、notify、notifyAll 的使用方法

public class ProducerConsumerInJava { 
    public static void main(String args[]) { 
        Queue buffer = new LinkedList<>(); 
        int maxSize = 10; 
        Thread producer = new Producer(buffer, maxSize, "PRODUCER"); 
        Thread consumer = new Consumer(buffer, maxSize, "CONSUMER"); 
        producer.start(); 
        consumer.start(); 
    } 
} 

/** 
 * Producer Thread will keep producing values for Consumer 
 * to consumer. It will use wait() method when Queue is full 
 * and use notify() method to send notification to Consumer 
 * Thread. 
 */
class Producer extends Thread { 
    private Queue queue; 
    private int maxSize; 
    public Producer(Queue queue, int maxSize, String name) { 
        super(name); 
        this.queue = queue; 
        this.maxSize = maxSize; 
    } 
    @Override 
    public void run() { 
        while (true) { 
            synchronized (queue) { 
                while (queue.size() == maxSize) { 
                    try { 
                        System.out.println("Queue is full, " + "producer thread waiting for " + "consumer to take something from queue"); 
                        queue.wait(); 
                    } catch (Exception ex) { 
                        ex.printStackTrace(); } 
                    } 
                    Random random = new Random(); 
                    int i = random.nextInt(); 
                    System.out.println("Producing value : " + i); 
                    queue.add(i); 
                    queue.notifyAll(); 
                } 
            } 
        } 
    } 
}

/** 
 * Consumer Thread will consumer values form shared queue. 
 * It will also use wait() method to wait if queue is 
 * empty. It will also use notify method to send 
 * notification to producer thread after consuming values 
 * from queue. 
 */
class Consumer extends Thread { 
    private Queue queue; 
    private int maxSize; 
    public Consumer(Queue queue, int maxSize, String name){ 
        super(name); 
        this.queue = queue; 
        this.maxSize = maxSize; 
    } 

    @Override 
    public void run() { 
        while (true) { 
            synchronized (queue) { 
                while (queue.isEmpty()) { 
                    System.out.println("Queue is empty, " + "consumer thread is waiting" + " for producer thread to put something in queue"); 
                        try { 
                            queue.wait(); 
                        } catch (Exception ex) { 
                            ex.printStackTrace(); 
                        } 
                    } 
                    System.out.println("Consuming value : " + queue.remove()); 
                    queue.notifyAll(); 
                } 
            } 
        } 
    }
}

总结

如果线程调用了对象的 wait() 方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁

当有线程调用了对象的 notifyAll() 方法(唤醒所有 wait 线程)或 notify() 方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁

优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait() 方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁

永远在 synchronized 的函数或对象里使用 wait、notify 和 notifyAll,不然会抛出
IllegalMonitorStateException

永远在 while 循环里而不是 if 语句下使用 wait。这样,循环会在线程睡眠前后都检查 wait 的条件,并在条件实际上并未改变的情况下处理唤醒通知(用 if 判断的话,唤醒后线程会从 wait 之后的代码开始运行,但是不会重新判断 if 条件,直接继续运行 if 代码块之后的代码,而如果使用 while 的话,也会从 wait 之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行 while 代码块之后的代码块,成立的话继续 wait)

永远在多线程间共享的对象(在生产者消费者模型里即缓冲区队列)上使用 wait

一般倾向用 notifyAll(),而不是 notify()

你可能感兴趣的:(Java 使用 wait / notify / notifyAll 实现线程间通信)