notify和notifyAll坑点

本文探讨了Java中线程同步机制的正确使用方法,特别是针对notifyAll()方法的使用进行了详细说明。通过一个具体的代码示例展示了如何避免IllegalMonitorStateException异常,并解释了为何必须在同步块内正确地获取对象锁。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.前言

优化Application时,由于上报uv和pv的操作初始需要读取配置和加载so库很多io操作,相对非常耗时,所以参考SharedPerfencesImpl的实现做了延迟加载,如果发现需要使用但是为初始完成会让调用线程wait.所以初始化结束时需要调用notifyall.

2. 问题

代码示例

public class Demo{
        public void init(){
            new Thread(){
                public void run() {
                      synchronized (Demo.this) {
                          //io操作
                          //初始化赋值操作
                          notifyAll();
                  }
                }
        }
}

在上面的同步块中直接调用notifyAll();这里会产生错误:

IllegalMonitorStateException: object not locked by thread before notify

当时一直不明白为何会产生这个错误,后面这么修改:

synchronized(this){
    notifyAll();
 }

当时没有报错,而且用户数据也上报成功,也就没有细节去分析.现在回头来看,突然发现这里的调用并不是正确的.正确的调用应该是

Demo.this.notifyAll();

public class Demo{
        public void init(){
            new Thread(){
                public void run() {
                      synchronized (Demo.this) {
                     loadInit();
                  }
                }
        }
        private void loadInit(){
             //io操作
            //初始化赋值操作
            notifyAll();
        }
}

3.原因

notifyall需要在同步块中,所以需要去获取当前对象的锁,而第一次直接调用是表示Thread.this.notifyAll();但是并没有去获取线程的锁.而加了synchronized(this){}不会报错了,是因为获取了Thread对象的锁,但是没有线程因为Thread对象wait.所以不是我们需要的效果.而后面的两种解决方案的实质是一样的.都是调用Demo.this对象的notifyall,但是后民使用方法替代,而没有使用Demo.this.notifyall的原因是,线程内部类隐式持有外部引用,而loadInit方法是外部Demo的方法,所以调用的时候是Demo.this.loadInit();所以执行notifyall的操作对象是Demo.this,而不是Thread.

### Java `notify` vs `notifyAll` 方法区别 #### 一、方法概述 在Java中,`notify()``notifyAll()`都是用于线程间通信的方法,属于`Object`类的一部分。这两个方法的主要功能是在特定条件下唤醒等待该对象锁的线程。 - **`notify()`**: 只唤醒一个处于等待状态的线程(如果有),具体哪一个由JVM决定[^1]。 - **`notifyAll()`**: 唤醒所有处于等待状态的线程。所有被唤醒的线程都将进入锁池并尝试重新获得对象锁[^2]。 #### 二、主要差异 ##### 1. 唤醒线程的数量 最直观的区别在于两者所影响的线程数目: - `notify()`仅会选择单一线程进行唤醒操作; - 而`notifyAll()`则会对所有符合条件的线程发出信号[^3]。 ##### 2. 应用场景的选择 根据不同情况选择合适的方法至关重要: - 当确切知道只需要唤醒单一指定线程时,可优先考虑使用`notify()`来降低不必要的资源消耗; - 若存在多个线程依赖于同一条件变化,则应采用`notifyAll()`确保每一个受影响的线程都能接收到通知[^4]。 ##### 3. 对性能的影响 由于`notifyAll()`会一次性激活大量线程参与竞争,这可能会造成短暂时间内CPU利用率上升;相反地,`notify()`因为作用范围较小,在某些情况下有助于提高效率[^5]。 #### 三、代码示例 为了更好地理解这两种方法的应用方式,这里给出一段简单的生产者-消费者模型作为示范: ```java public class ProducerConsumerExample { private static final int CAPACITY = 5; private Queue<Integer> queue = new LinkedList<>(); public synchronized void produce(int value) throws InterruptedException { while (queue.size() >= CAPACITY) { wait(); // 如果队列已满,则让当前线程等待 } queue.add(value); System.out.println("Produced: " + value); notifyAll(); // 生产完成后通知所有等待中的消费线程 } public synchronized Integer consume() throws InterruptedException { while (queue.isEmpty()) { wait(); // 队列为空时使消费线程暂停 } Integer consumedValue = queue.poll(); System.out.println("Consumed: " + consumedValue); notifyAll(); // 消费后同样要告知其他可能存在的生产线程 return consumedValue; } } ``` 在这个例子中,每当有新的商品加入库存(`produce`)或是现有商品被取出(`consume`)之后都会调用`notifyAll()`去提醒那些正处于休眠状态下的对方角色(即生产消费),从而维持整个系统的正常运转。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值