Java的notify()

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()时应注意以下几点:

  1. 锁的释放和获取: 当wait()方法被调用时,线程会释放它持有的锁。当notify()notifyAll()被调用时,等待状态的线程不会立即释放它们所等待的对象的锁,而是要等到调用notify()notifyAll()的线程退出同步块,释放锁之后,等待线程才有机会获取锁。

  2. 随机唤醒notify()方法会随机唤醒在该对象上等待的线程之一。如果你希望唤醒所有等待的线程,应该使用notifyAll()方法。

  3. 死锁风险: 如果不当使用wait()notify(),特别是在复杂的同步策略中,可能会导致死锁或者活锁。

  4. 必须在同步块中调用wait()notify(), 和 notifyAll() 必须在synchronized方法或synchronized块内部调用。

  5. 监视器的所有权: 只有对对象监视器拥有所有权的线程才能调用wait()notify(), 或 notifyAll() 方法。

  6. 异常处理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()方法时会在栅栏处等待,只有最后一个例子被意外截断了,我们来完成它。

CyclicBarrierExampleawait()方法中,每个线程在调用该方法时会在栅栏处等待,只有当所有线程都到达栅栏时,它们才会一起继续执行。这是通过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包中的CyclicBarrierCountDownLatchSemaphore等,这些API提供了更易于使用和理解的抽象,并且在内部已经处理了许多复杂的同步细节。

 

  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值