Java中的notify()
是一个用于线程间通信的方法,属于Object
类的一部分。当多个线程间需要协调执行顺序或者资源共享时,notify()
可以用来唤醒正在等待对象监视器(monitor)的某一个线程。
在Java中,任何对象都可以作为同步锁,这是因为每个对象都有一个与之关联的监视器。当一个线程进入一个synchronized同步块或方法时,它会自动获取这个对象的监视器锁。如果另一个线程试图进入由同一锁保护的另一个synchronized同步块或方法,那么它将会阻塞,直到锁被释放。
如果一个线程调用了某个对象上的wait()
方法,它会释放该对象的锁并且进入等待状态,直到其他线程在同一个对象上调用notify()
或notifyAll()
方法。这时,wait()
方法的线程可能会被唤醒(如果有多个线程在等待,则只有一个会被随机选中唤醒),并且在notify()
被调用后,继续尝试重新获取锁。一旦获取锁之后,线程可以继续执行。
下面是一个使用notify()
的例子:
public class Example {
public synchronized void waitForSignal() {
try {
while (someConditionIsNotMet()) {
wait();
}
// 执行当条件满足时的动作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public synchronized void doSomething() {
// 更改条件,可能会让等待线程执行
changeSomeConditions();
notify(); // 唤醒等待这个对象监视器的一个线程
}
}
在使用notify()
时应注意以下几点:
-
锁的释放和获取: 当
wait()
方法被调用时,线程会释放它持有的锁。当notify()
或notifyAll()
被调用时,等待状态的线程不会立即释放它们所等待的对象的锁,而是要等到调用notify()
或notifyAll()
的线程退出同步块,释放锁之后,等待线程才有机会获取锁。 -
随机唤醒:
notify()
方法会随机唤醒在该对象上等待的线程之一。如果你希望唤醒所有等待的线程,应该使用notifyAll()
方法。 -
死锁风险: 如果不当使用
wait()
和notify()
,特别是在复杂的同步策略中,可能会导致死锁或者活锁。 -
必须在同步块中调用:
wait()
,notify()
, 和notifyAll()
必须在synchronized方法或synchronized块内部调用。 -
监视器的所有权: 只有对对象监视器拥有所有权的线程才能调用
wait()
,notify()
, 或notifyAll()
方法。 -
异常处理:
wait()
方法会抛出InterruptedException
异常,因此需要正确处理中断异常。
总之,notify()
是线程间协作的核心方法之一,但它需要谨慎使用,以避免复杂的线程同步问题。
Java的notify()
方法主要用于唤醒在当前对象的wait set中等待的单个线程。下面是一些使用notify()
的示例,它们展示了不同场景下如何使用这个方法。
示例 1: 生产者-消费者问题
这是一个典型的多线程同步问题,其中生产者线程生成数据,而消费者线程消费这些数据。notify()
用于通知等待的线程数据已经可用或可存放。
public class ProducerConsumerExample {
private LinkedList<Integer> list = new LinkedList<>();
private final int LIMIT = 10;
private Object lock = new Object();
public void produce() throws InterruptedException {
int value = 0;
while (true) {
synchronized (lock) {
while (list.size() == LIMIT) {
lock.wait();
}
list.add(value++);
lock.notify();
}
}
}
public void consume() throws InterruptedException {
while (true) {
synchronized (lock) {
while (list.size() == 0) {
lock.wait();
}
int value = list.removeFirst();
System.out.println("Consumed value: " + value);
lock.notify();
}
Thread.sleep(1000); // This could be some processing time.
}
}
}
在这个例子中,produce()
方法在添加一个元素到列表后会调用notify()
来唤醒可能正在等待的消费者线程。同样,consume()
方法在消费一个元素后会调用notify()
以唤醒可能正在等待的生产者线程。
示例 2: 等待特定条件
有时候,线程需要等待特定条件成立才能继续执行。下面是一个简单的例子,其中线程等待isReady
变为true
。
public class WaitNotifyExample {
private boolean isReady = false;
public synchronized void waitForCondition() throws InterruptedException {
while (!isReady) {
wait();
}
// 执行后续操作
System.out.println("Condition is ready, proceeding with operation.");
}
public synchronized void changeCondition() {
isReady = true;
notify(); // 唤醒在这个对象监视器上等待的线程
}
}
示例 3: 顺序执行控制
有时候,线程需要按照一定的顺序执行。例如,线程A需要在线程B之前执行完成。
public class OrderedExecution {
private boolean isFirstFinished = false;
public void first() {
synchronized (this) {
// 执行第一个任务的操作
isFirstFinished = true;
notify();
}
}
public void second() {
synchronized (this) {
while (!isFirstFinished) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 执行第二个任务的操作
}
}
}
在上述例子中,second()
方法会等待first()
方法执行完成(即isFirstFinished
变为true
)之后才开始执行。
示例 4: 使用notify()
与wait()
实现循环栅栏
某些情况下,可能需要多个线程在继续执行前达到某个公共点,这种场景可以使用CyclicBarrier来实现,但也可以手动通过wait()
和notify()
实现简化版。
public class CyclicBarrierExample {
private int arrived = 0;
private final int totalThreads;
private final Object lock = new Object();
public CyclicBarrierExample(int totalThreads) {
this.totalThreads = totalThreads;
}
public void await() throws InterruptedException {
synchronized (lock) {
arrived++;
if (arrived == totalThreads) {
arrived = 0; // 重置以便下一次使用
lock.notifyAll(); // 唤醒所有等待的线程
} else {
while (arrived < totalThreads) {
lock.wait();
}
}
}
}
}
在这个例子中,每个线程在调用await()
方法时会在栅栏处等待,只有最后一个例子被意外截断了,我们来完成它。
在CyclicBarrierExample
的await()
方法中,每个线程在调用该方法时会在栅栏处等待,只有当所有线程都到达栅栏时,它们才会一起继续执行。这是通过arrived
计数器来追踪到达的线程数量实现的,一旦arrived
等于totalThreads
,意味着所有线程都到达了,然后通过notifyAll()
唤醒所有等待的线程。
public void await() throws InterruptedException {
synchronized (lock) {
arrived++;
if (arrived == totalThreads) {
arrived = 0; // 重置以便下一次使用
lock.notifyAll(); // 唤醒所有等待的线程
} else {
while (arrived < totalThreads) {
lock.wait();
}
}
}
}
这种实现方式需要注意的是,它不是一个完全的CyclicBarrier
替代品,因为它没有处理中断或超时的情况,并且如果await()
在所有线程被释放后再次被调用,那么arrived
计数器会被重置,这可能不是你想要的行为。正确使用CyclicBarrier
通常是更安全和更方便的多线程同步机制。
notify()
方法的使用需要仔细考虑线程间的协调和同步条件,确保没有线程会永久地等待下去,同时避免潜在的竞争条件或死锁。在设计多线程程序时,通常推荐使用高级的并发API,如java.util.concurrent
包中的CyclicBarrier
、CountDownLatch
、Semaphore
等,这些API提供了更易于使用和理解的抽象,并且在内部已经处理了许多复杂的同步细节。