Java基础多线程(七)Condition

本文介绍了Java中Condition接口如何解决多线程中的过早唤醒问题,通过实例展示了Condition相比wait/notify更灵活的多路通知和选择性通知。通过await()、signal()和signalAll()三个核心函数的分析,阐述了Condition如何实现线程的精确唤醒,降低了不必要的上下文切换,提高了性能。

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

概述

在之前的博客中介绍了wait()和notify()/notifAll(),它们只能选择随机唤醒或者全部唤醒等待锁的线程,如果遇到多个保护条件时容易导致过早唤醒问题,而Condition接口很好的解决了这个问题,它十分灵活,可以实现多路通知和选择性通知。文字有点抽象,看例子就明白了。

使用示例

场景:肯德基的货架中可以摆放各种食品,假设每个柜台只能卖一种食品,后厨可以生产各种食品放在货架上,当一种食品缺货时对应柜台则暂停出售,后厨补充了对应食品时会喊前台人员继续出售。

  • 使用synchronized的写法:
public class Demo {

    static final Object obj = new Object();
    static int hamburger = 5;
    static int chips = 0;
    
     static class Cook extends Thread{
        @Override
        public void run() {
            try {
                while (true) {
                    if (hamburger ==  0 ) {
                        synchronized (obj) {
                            hamburger++;
                            obj.notifyAll();
                        }
                    }
                    Thread.sleep(1000);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    static class HamburgerCounter extends Thread{
        @Override
        public void run() {
            try {
                for (;;) {
                    while (hamburger > 0) {
                        System.out.println("HamburgerCounter卖出了第" + hamburger + "个汉堡");
                        hamburger--;
                        Thread.sleep(500);
                    }
                    synchronized (obj) {
                        System.out.println("汉堡已卖完,进入等待");
                        obj.wait();
                        System.out.println("收到通知,继续卖汉堡");
                    }

                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class ChipsCounter extends Thread{
        @Override
        public void run() {
            try {
                for (;;) {
                    while (chips == 0) {
                        synchronized (obj) {
                            System.out.println("薯条已卖完,进入等待");
                            obj.wait();
                            System.out.println("收到通知,继续卖薯条");
                        }
                    }
                    chips--;
                    System.out.println("ChipsCounter卖出了第" + chips + "包薯条");
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new Cook().start();
        new HamburgerCounter().start();
        new ChipsCounter().start();
    }
}

运行结果:

HamburgerCounter卖出了第5个汉堡
HamburgerCounter卖出了第4个汉堡
HamburgerCounter卖出了第3个汉堡
HamburgerCounter卖出了第2个汉堡
HamburgerCounter卖出了第1个汉堡
薯条已卖完,进入等待
汉堡已卖完,进入等待
收到通知,继续卖汉堡
收到通知,继续卖薯条
薯条已卖完,进入等待
HamburgerCounter卖出了第1个汉堡
汉堡已卖完,进入等待
收到通知,继续卖汉堡
HamburgerCounter卖出了第1个汉堡
收到通知,继续卖薯条
薯条已卖完,进入等待
汉堡已卖完,进入等待

由运行结果可知,在Cook中生产汉堡后,唤醒等待的柜台notifyAll时,唤醒了HamburgerCounter和ChipsCounter ,HamburgerCounter继续卖汉堡,ChipsCounter线程检测到Chips为0于是再次进入等待。这样做虽然完成了需求,但是唤醒了无需唤醒的线程,这样会触发多余的上下文切换影响性能。

  • 使用condition写法
public class Demo {

    static Lock lock = new ReentrantLock();
    static Condition hamburgerCounter = lock.newCondition();
    static Condition chipsCounter = lock.newCondition();

    static int hamburger = 3;
    static int chips = 0;

    static class Cook extends Thread {
        @Override
        public void run() {
            try {
                while (true) {
                    if (hamburger == 0) {
                        try {
                            lock.lock();
                            hamburger++;
                            hamburgerCounter.signalAll();
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            lock.unlock();
                        }
                    }
                    Thread.sleep(1000);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    static class HamburgerCounter extends Thread {
        @Override
        public void run() {
            try {
                for (; ; ) {
                    while (hamburger > 0) {
                        System.out.println("HamburgerCounter卖出了第" + hamburger + "个汉堡");
                        hamburger--;
                        Thread.sleep(500);
                    }
                    try {
                        lock.lock();
                        System.out.println( "汉堡已卖完,进入等待");
                        hamburgerCounter.await();
                        System.out.println("收到通知,继续卖汉堡");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

	static class ChipsCounter extends Thread {
        @Override
        public void run() {
            try {
                for (; ; ) {
                    while (chips > 0) {
                        System.out.println("ChipsCounter卖出了第" + chips + "份薯条");
                        chips--;
                        Thread.sleep(500);
                    }
                    try {
                        lock.lock();
                        System.out.println("薯条已卖完,进入等待");
                        chipsCounter.await();
                        System.out.println("收到通知,继续卖薯条");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new Cook().start();
        new HamburgerCounter().start();
        new ChipsCounter().start();
    }
}

运行结果:

HamburgerCounter卖出了第3个汉堡
薯条已卖完,进入等待
HamburgerCounter卖出了第2个汉堡
HamburgerCounter卖出了第1个汉堡
汉堡已卖完,进入等待
收到通知,继续卖汉堡
HamburgerCounter卖出了第1个汉堡
汉堡已卖完,进入等待
收到通知,继续卖汉堡
HamburgerCounter卖出了第1个汉堡

在这个案例中,使用condition,生产了汉堡就精准的唤醒卖汉堡的线程,不会将卖薯条的唤醒,避免了过早唤醒的问题。

核心函数分析

await()

  • 使线程进入等待,和Object.wait()类似,必须在当前线程持有Lock时才能调用。
  • 每个Condition都会维护一个等待队列,等待队列在AQS中以内部类实现(ConditionObject)。
  • 调用await方法时,释放锁唤醒同步队列中的后继节点,将当前线程加入相应的condition对象的等待队列中并令它阻塞。
  • 当此线程被唤醒时会被加入到同步队列中竞争锁,直到竞争到了锁,await方法才会返回。

signal()

  • 唤醒此condition等待队列中的首节点(等待最久的节点),和Object.notify()类似,必须在当前线程持有Lock时才能调用。
  • 将等待队列中的首节点移动到同步队列中并唤醒它。

signalAll()

  • 唤醒此condition等待队列中的所有节点,和Object.notifyAll()类似,必须在当前线程持有Lock时才能调用。
  • 将等待队列中的全部节点移动到同步队列中,并唤醒每个节点的线程。

总结

condition是对锁更加精准的控制,使用方法大致和Object中的唤醒和等待机制类似,但是比它拥有更高的灵活性,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。

更详细的学习condition推荐这篇博客:https://www.cnblogs.com/sheeva/p/6484224.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值