跳转至

Java Notify and Wait:线程间通信的关键机制

简介

在多线程编程中,线程间的有效通信至关重要。Java 的 notify()wait() 方法提供了一种强大的机制,用于协调不同线程之间的操作。通过这两个方法,线程可以暂停执行(wait()),并在特定条件满足时被其他线程唤醒(notify())。本文将深入探讨 notify()wait() 的概念、使用方法、常见实践及最佳实践。

目录

  1. 基础概念
  2. 使用方法
    • wait() 方法
    • notify() 方法
    • notifyAll() 方法
  3. 常见实践
    • 生产者 - 消费者模式
    • 线程同步控制
  4. 最佳实践
    • 避免死锁
    • 合理使用 notifyAll()
    • 条件判断使用 while 而非 if
  5. 小结
  6. 参考资料

基础概念

wait()notify()java.lang.Object 类的方法,这意味着所有 Java 对象都具备这两个方法。它们主要用于线程间的通信,通过共享对象的监视器(monitor)来实现。

监视器(Monitor)

每个对象都有一个与之关联的监视器。当一个线程访问对象的同步方法或同步块时,它会获取该对象的监视器。在持有监视器期间,其他线程无法访问该对象的同步代码,直到该线程释放监视器。

wait() 方法

wait() 方法用于使当前线程等待,直到其他线程调用该对象的 notify()notifyAll() 方法。调用 wait() 方法时,当前线程会释放对象的监视器,进入等待状态。当被唤醒后,线程会重新获取监视器,然后继续执行。

notify() 方法

notify() 方法用于唤醒在此对象监视器上等待的单个线程。如果有多个线程在等待,则选择其中一个唤醒,选择是任意的,由 JVM 决定。

notifyAll() 方法

notifyAll() 方法用于唤醒在此对象监视器上等待的所有线程。所有被唤醒的线程会竞争获取对象的监视器,然后继续执行。

使用方法

wait() 方法

wait() 方法有三种重载形式:

public final void wait() throws InterruptedException
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException

示例代码:

public class WaitExample {
    public static void main(String[] args) {
        Object lock = new Object();
        Thread thread = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("线程开始等待...");
                    lock.wait();
                    System.out.println("线程被唤醒...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        synchronized (lock) {
            lock.notify();
            System.out.println("主线程唤醒了等待线程");
        }
    }
}

notify() 方法

notify() 方法是 Object 类的实例方法,调用时需要在同步块或同步方法中进行,以确保线程持有对象的监视器。 示例代码:

public class NotifyExample {
    public static void main(String[] args) {
        Object lock = new Object();
        Thread thread = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("线程开始等待...");
                    lock.wait();
                    System.out.println("线程被唤醒...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        synchronized (lock) {
            lock.notify();
            System.out.println("主线程唤醒了等待线程");
        }
    }
}

notifyAll() 方法

notifyAll() 方法同样需要在同步块或同步方法中调用,用于唤醒所有等待在该对象监视器上的线程。 示例代码:

public class NotifyAllExample {
    public static void main(String[] args) {
        Object lock = new Object();
        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("线程1开始等待...");
                    lock.wait();
                    System.out.println("线程1被唤醒...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("线程2开始等待...");
                    lock.wait();
                    System.out.println("线程2被唤醒...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread1.start();
        thread2.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        synchronized (lock) {
            lock.notifyAll();
            System.out.println("主线程唤醒了所有等待线程");
        }
    }
}

常见实践

生产者 - 消费者模式

生产者 - 消费者模式是多线程编程中的经典场景,通过 wait()notify() 方法可以有效实现。

import java.util.LinkedList;
import java.util.Queue;

class Producer implements Runnable {
    private final Queue<Integer> queue;
    private final int capacity;

    public Producer(Queue<Integer> queue, int capacity) {
        this.queue = queue;
        this.capacity = capacity;
    }

    @Override
    public void run() {
        int value = 0;
        while (true) {
            synchronized (queue) {
                while (queue.size() == capacity) {
                    try {
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                queue.add(value++);
                System.out.println("生产: " + value);
                queue.notify();
            }
        }
    }
}

class Consumer implements Runnable {
    private final Queue<Integer> queue;

    public Consumer(Queue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (queue) {
                while (queue.isEmpty()) {
                    try {
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                int value = queue.poll();
                System.out.println("消费: " + value);
                queue.notify();
            }
        }
    }
}

public class ProducerConsumerExample {
    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<>();
        int capacity = 5;

        Thread producerThread = new Thread(new Producer(queue, capacity));
        Thread consumerThread = new Thread(new Consumer(queue));

        producerThread.start();
        consumerThread.start();
    }
}

线程同步控制

在某些场景下,需要控制多个线程按照特定顺序执行,wait()notify() 可以实现这种同步。

public class ThreadSyncExample {
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("线程1执行");
                lock1.notify();
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock1) {
                try {
                    lock1.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程2执行");
                synchronized (lock2) {
                    lock2.notify();
                }
            }
        });

        Thread thread3 = new Thread(() -> {
            synchronized (lock2) {
                try {
                    lock2.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程3执行");
            }
        });

        thread2.start();
        thread1.start();
        thread3.start();
    }
}

最佳实践

避免死锁

死锁是多线程编程中常见的问题,当两个或多个线程相互等待对方释放资源时,就会发生死锁。为避免死锁,应注意以下几点: - 尽量减少锁的使用范围和时间。 - 确保线程获取锁的顺序一致。 - 使用 tryLock() 方法尝试获取锁,避免无限等待。

合理使用 notifyAll()

notifyAll() 会唤醒所有等待线程,可能导致性能问题。应尽量使用 notify() 唤醒单个线程,只有在需要唤醒所有等待线程时才使用 notifyAll()

条件判断使用 while 而非 if

在使用 wait() 方法时,应使用 while 循环进行条件判断,而不是 if。因为线程被唤醒后,可能由于其他原因导致条件不再满足,使用 while 可以确保在条件真正满足时才继续执行。

synchronized (lock) {
    while (!condition) {
        try {
            lock.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    // 执行相应操作
}

小结

notify()wait() 方法是 Java 多线程编程中实现线程间通信的重要机制。通过合理使用这两个方法,可以实现复杂的多线程同步和协作。在实际应用中,需要注意避免死锁、合理使用 notifyAll() 以及正确使用条件判断,以确保程序的正确性和性能。

参考资料