Condition案例和synchronized案例对比

一、先看下 Condition的使用案例:

经典案例:生产者-消费者问题(面包店模型)

/**
 * 面包店:生产者做面包,消费者买面包
 * - 货架最多放5个面包
 * - 货架满时,生产者要等待
 * - 货架空时,消费者要等待
 */
public class Bakery {
    private final Queue<String> shelf = new LinkedList<>(); // 货架
    private final int CAPACITY = 5; // 货架容量
    
    private final Lock lock = new ReentrantLock();
    
    // 创建两个条件:货架"非满"和"非空"
    private final Condition notFull = lock.newCondition();  // 用于生产者等待
    private final Condition notEmpty = lock.newCondition(); // 用于消费者等待
    
    /**
     * 生产者:制作面包
     */
    public void produceBread(String breadName) throws InterruptedException {
        lock.lock();
        try {
            // 如果货架满了,生产者就要等待"货架非满"这个条件
            while (shelf.size() == CAPACITY) {
                System.out.println("货架已满," + Thread.currentThread().getName() + " 等待中...");
                notFull.await(); // 释放锁,进入等待状态
            }
            
            // 货架有空间了,放上面包
            shelf.offer(breadName);
            System.out.println(Thread.currentThread().getName() + " 制作了: " + breadName + 
                             ",货架现有: " + shelf.size() + "个面包");
            
            // 通知消费者:货架现在"非空"了,可以来买了
            notEmpty.signal();
            
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 消费者:购买面包
     */
    public String consumeBread() throws InterruptedException {
        lock.lock();
        try {
            // 如果货架空了,消费者就要等待"货架非空"这个条件
            while (shelf.isEmpty()) {
                System.out.println("货架已空," + Thread.currentThread().getName() + " 等待中...");
                notEmpty.await(); // 释放锁,进入等待状态
            }
            
            // 货架有面包了,取走一个
            String bread = shelf.poll();
            System.out.println(Thread.currentThread().getName() + " 购买了: " + bread + 
                             ",货架剩余: " + shelf.size() + "个面包");
            
            // 通知生产者:货架现在"非满"了,可以继续生产了
            notFull.signal();
            
            return bread;
            
        } finally {
            lock.unlock();
        }
    }
}

测试代码

public class BakeryTest {
    public static void main(String[] args) {
        Bakery bakery = new Bakery();
        
        // 创建2个生产者线程
        for (int i = 1; i <= 2; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 10; j++) {
                        bakery.produceBread("面包-" + Thread.currentThread().getName() + "-" + j);
                        Thread.sleep(100); // 模拟制作时间
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }, "生产者" + i).start();
        }
        
        // 创建3个消费者线程
        for (int i = 1; i <= 3; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 7; j++) {
                        bakery.consumeBread();
                        Thread.sleep(150); // 模拟购买时间
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }, "消费者" + i).start();
        }
    }
}

完整流程分析

场景1:货架未满,多个生产者竞争

// 假设当前货架有3个面包(未满)
// 生产者1和生产者2同时到达

生产者1: lock.lock()  成功获取锁
生产者2: lock.lock()  ❌ 阻塞等待(因为锁被生产者1持有)

// 生产者1继续执行
生产者1: 检查货架未满 → 制作面包 → notEmpty.signal() → lock.unlock()

// 生产者1释放锁后
生产者2: 从阻塞状态被唤醒,成功获取锁 → 检查货架 → 制作面包...

关键点:在货架未满时,生产者之间是公平竞争锁的关系。

场景2:货架已满,Condition 开始发挥作用

// 假设货架已经有5个面包(满了)
// 生产者1先获取锁
生产者1: lock.lock() 成功
生产者1: 检查货架已满 → notFull.await()

// 重点来了!在await() 方法内部:
1. 生产者1**自动释放锁**(这样其他线程就能获取锁了)
2. 生产者1线程进入等待状态,被挂起

// 此时锁被释放,消费者可以获取锁了
消费者1: lock.lock() 成功获取锁
消费者1: 购买面包 → notFull.signal() → lock.unlock()

// notFull.signal() 唤醒了在 notFull 条件上等待的生产者1
生产者1:await() 中醒来,但需要**重新获取锁**才能继续执行

注意:生产者因为调用**notFull.await()**进入notFull队列等待,并且会释放锁,当消费者通过notFull.signal()唤醒生成者的时候,是从notFull.await()这行代码继续执行,只不过还要获取锁,如果没有获取到就要等待,但是他不需要写lock.lock();这种代码。这里需要注意,生产者执行notFull.await()代码,只是释放锁和进入等待,没有做唤醒消费者动作。

notFull.await()做了什么

//1、也就是执行notFull.await()这个代码,里面做了好几个事情,先创建等待队列,
//2、然后释放锁,自己挂起等待,唤醒后还要在获取一次锁。用伪代码表示就是下面:
public final void await() throws InterruptedException {
    // 1. 创建新的节点加入到条件队列
    Node node = addConditionWaiter();
    
    // 2. 完全释放当前线程持有的锁(让其他线程可以获取锁)
    int savedState = fullyReleaseLock();
    
    // 3. 将当前线程挂起,进入等待状态
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
    }
    
    // 4. 被唤醒后,需要重新获取锁
    acquireQueued(node, savedState);
}
生产者1获取锁 → 检查货架已满 → 调用 notFull.await()
                    ↓
            自动释放锁,线程挂起
                    ↓
消费者获取锁 → 购买面包 → 调用 notFull.signal()
                    ↓
          生产者1被唤醒,尝试重新获取锁
                    ↓
    成功获取锁后,从await()方法继续执行

整体执行流程:

生产者1 尝试获取锁...
生产者1 成功获取锁
生产者2 尝试获取锁...           ← 生产者2在这里阻塞
货架已满,生产者1 开始等待...     ← 生产者1调用await()释放锁
生产者2 成功获取锁               ← 生产者2立即获取到锁
货架已满,生产者2 也开始等待...     ← 生产者2调用await()释放锁

消费者1 尝试获取锁...
消费者1 成功获取锁
消费者1 购买了面包
消费者1 唤醒了生产者1
消费者1 释放锁
生产者1 被唤醒,重新检查条件      ← 生产者1需要重新获取锁
生产者1 成功获取锁              ← 获取锁成功
生产者1 制作了: 面包+1
生产者1 释放锁

问题

问题:当多个生产者调用notFull.await();后,被消费者唤醒,那么唤醒的顺序是按进入notFull的顺序唤醒的吗?

是的,FIFO(先进先出)顺序唤醒的,被唤醒的顺序严格按进入 await()的顺序,
与锁的公平性无关。但是获取锁的顺序,这取决于使用的 Lock 是公平锁还是非公平锁。

二、如果使用synchronized实现:

/**
 * 使用 synchronized 实现的有界缓冲区
 */
public class BoundedBufferSynchronized {
    private final Queue<String> buffer = new LinkedList<>();
    private final int capacity;
    
    public BoundedBufferSynchronized(int capacity) {
        this.capacity = capacity;
    }
    
    /**
     * 生产者方法
     */
    public void produce(String item) throws InterruptedException {
        synchronized (this) {  // 使用 synchronized 块
            // 如果缓冲区已满,等待
            while (buffer.size() == capacity) {
                System.out.println(Thread.currentThread().getName() + ": 缓冲区已满,等待中...");
                wait();  // 释放锁,进入等待
            }
            
            // 生产物品
            buffer.offer(item);
            System.out.println(Thread.currentThread().getName() + " 生产了: " + item + ",当前数量: " + buffer.size());
            
            // 通知所有等待的线程(包括生产者和消费者)
            notifyAll();
        }
    }
    
    /**
     * 消费者方法
     */
    public String consume() throws InterruptedException {
        synchronized (this) {
            // 如果缓冲区为空,等待
            while (buffer.isEmpty()) {
                System.out.println(Thread.currentThread().getName() + ": 缓冲区为空,等待中...");
                wait();  // 释放锁,进入等待
            }
            
            // 消费物品
            String item = buffer.poll();
            System.out.println(Thread.currentThread().getName() + " 消费了: " + item + 
                             ",当前数量: " + buffer.size());
            
            // 通知所有等待的线程
            notifyAll();
            
            return item;
        }
    }
    
    public synchronized int size() {
        return buffer.size();
    }
}

测试代码:

public class SynchronizedProducerConsumerTest {
    public static void main(String[] args) {
        BoundedBufferSynchronized buffer = new BoundedBufferSynchronized(3);
        
        // 生产者线程
        Thread producer1 = new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    buffer.produce("产品-" + Thread.currentThread().getName() + "-" + i);
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "生产者1");
        
        Thread producer2 = new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    buffer.produce("产品-" + Thread.currentThread().getName() + "-" + i);
                    Thread.sleep(150);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "生产者2");
        
        // 消费者线程
        Thread consumer1 = new Thread(() -> {
            try {
                for (int i = 1; i <= 6; i++) {
                    buffer.consume();
                    Thread.sleep(200);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "消费者1");
        
        Thread consumer2 = new Thread(() -> {
            try {
                for (int i = 1; i <= 4; i++) {
                    buffer.consume();
                    Thread.sleep(180);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "消费者2");
        
        producer1.start();
        producer2.start();
        consumer1.start();
        consumer2.start();
        
        try {
            producer1.join();
            producer2.join();
            consumer1.join();
            consumer2.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        System.out.println("最终缓冲区大小: " + buffer.size());
    }
}

synchronized 实现的严重缺点

“惊群效应”(最严重的缺点)

生产者

while (buffer.size() == capacity) {
    wait();  // 生产者等待
}
// ... 生产面包 ...
notifyAll();  // 唤醒所有线程

消费者

while (buffer.isEmpty()) {
    wait();  // 消费者等待  
}
// ... 消费 ...
notifyAll();  // 唤醒所有线程

问题分析:

当生产者生产一个产品后调用 notifyAll(),会唤醒所有等待的线程
包括:其他生产者 + 所有消费者
但被唤醒的生产者发现缓冲区仍然满,只能继续等待
造成大量不必要的线程唤醒和上下文切换。大量不必要的线程唤醒、频繁的线程上下文切换、CPU 资源浪费、在高并发场景下性能严重下降

无法实现精确通知

// 我们真正想要的是:
生产者生产 → 只唤醒消费者
消费者消费 → 只唤醒生产者

// 但 synchronized 只能:
生产者生产 → 唤醒所有线程(生产者和消费者)
消费者消费 → 唤醒所有线程(生产者和消费者)
  1. 性能问题
由于频繁的"惊群效应",导致:大量不必要的线程唤醒、频繁的线程上下文切换、
CPU 资源浪费、在高并发场景下性能严重下降

总结:synchronized 实现的缺点

1、惊群效应:notifyAll()唤醒所有线程,造成大量不必要的唤醒
2、无法精确通知:不能针对特定条件的线程进行唤醒
3、性能较差:在高并发场景下,频繁的无效唤醒导致性能下降
4、功能有限:缺少超时、中断等高级特性
5、公平性不可控:无法实现公平锁

什么时候可以用 synchronized?

1、虽然有这么多的缺点,但 synchronized在以下场景仍然适用:
2、简单的同步场景:线程数量少,竞争不激烈
3、代码简洁性优先:快速开发,逻辑简单
4、性能要求不高:不是性能关键路径

但对于复杂的生产者-消费者场景,Lock + Condition 是明显更好的选择。

(Kriging_NSGA2)克里金模型结合多目标遗传算法求最优因变量及对应的最佳自变量组合研究(Matlab代码实现)内容概要:本文介绍了克里金模型(Kriging)与多目标遗传算法NSGA-II相结合的方法,用于求解最优因变量及其对应的最佳自变量组合,并提供了完整的Matlab代码实现。该方法首先利用克里金模型构建高精度的代理模型,逼近复杂的非线性系统响应,减少计算成本;随后结合NSGA-II算法进行多目标优化,搜索帕累托前沿解集,从而获得多个最优折衷方案。文中详细阐述了代理模型构建、算法集成流程及参数设置,适用于工程设计、参数反演等复杂优化问题。此外,文档还展示了该方法在SCI一区论文中的复现应用,体现了其科学性与实用性。; 适合人群:具备一定Matlab编程基础,熟悉优化算法数值建模的研究生、科研人员及工程技术人员,尤其适合从事仿真优化、实验设计、代理模型研究的相关领域工作者。; 使用场景及目标:①解决高计算成本的多目标优化问题,通过代理模型降低仿真次数;②在无法解析求导或函数高度非线性的情况下寻找最优变量组合;③复现SCI高水平论文中的优化方法,提升科研可信度与效率;④应用于工程设计、能源系统调度、智能制造等需参数优化的实际场景。; 阅读建议:建议读者结合提供的Matlab代码逐段理解算法实现过程,重点关注克里金模型的构建步骤与NSGA-II的集成方式,建议自行调整测试函数或实际案例验证算法性能,并配合YALMIP等工具包扩展优化求解能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

信仰_273993243

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值