01-PC模式的几种实现(面试题)

本文深入探讨了生产者消费者模式的多种实现方式,包括synchronized同步、wait-notify、Condition和同步队列等,对比了各自的优缺点,并通过具体代码示例展示了如何避免常见错误。

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

生产者消费者模式

一、synchronized同步

  • 代码
public class PcSync {

    static int MAX = 10;
    static Queue<Object> queue = new ArrayDeque<>(MAX);

    public static void main(String[] args) {

        Producer p = new Producer();
        Consumer c1 = new Consumer();
        Consumer c2 = new Consumer();
        new Thread(p).start();
        new Thread(p).start();
        new Thread(c1).start();
        new Thread(c2).start();
    }


    static class Producer implements Runnable {

        @Override
        public void run() {
            while (true) {
                synchronized (PcSync.class) {
                    if (queue.size() < MAX) {
                        queue.add(new Object());
                        System.out.println("生产一个,生产后有商品" + queue.size() + "个");
                        SleepTools.randomMs(400);
                    } else {
                        System.out.println("满了,不能再生产了...");
                    }
                }
            }
        }
    }

    static class Consumer implements Runnable {

        @Override
        public void run() {
            while (true) {
                synchronized (PcSync.class) {
                    if (queue.size() > 0) {
                        queue.remove();
                        System.out.println("消费一个,消费后有商品" + queue.size() + "个");
                        SleepTools.randomMs(400);
                    } else {
                        System.out.println("空了,不能再消费了...");
                    }
                }
            }
        }
    }
}

二、wait-notify

  • 代码
public class PcWaitNotify {

    static int MAX = 10;
    static Queue<Object> queue = new ArrayDeque<>(MAX);

    public static void main(String[] args) {
        Producer p = new Producer();
        Consumer c1 = new Consumer();
        Consumer c2 = new Consumer();
        new Thread(p).start();
        new Thread(p).start();
        new Thread(c1).start();
        new Thread(c2).start();
    }


    static class Producer implements Runnable {

        @Override
        public void run() {
            while (true) {
                synchronized (queue) {
                    if (queue.size() >= MAX) {
                        queue.notifyAll();
                    } else {
                        queue.add(new Object());
                        System.out.println("生产者生产了 1 个,现在有:" + queue.size() + " 个商品...");
                        SleepTools.randomMs(500);
                    }
                }
            }
        }
    }

    static class Consumer implements Runnable {

        @Override
        public void run() {
            while (true) {
                synchronized (queue) {
                    if (queue.size() <= 0) {
                        queue.notifyAll();
                    } else {
                        queue.remove();
                        System.out.println("消费者消费了 1 个,现在有:" + queue.size() + " 个商品...");
                        SleepTools.randomMs(500);
                    }
                }
            }
        }
    }
}

三、Condition

  • 代码
public class PcCondition {

    private static ReentrantLock lock = new ReentrantLock();
    private static Condition consumer = lock.newCondition();
    private static Condition producer = lock.newCondition();
    private static LinkedList<Object> buffer = new LinkedList<>();
    private static final int MAX = 5;

    public static void main(String[] args) {
        new Thread(new Producer()).start();
        new Thread(new Producer()).start();
        new Thread(new Consumer()).start();
        new Thread(new Consumer()).start();
    }


    static class Producer implements Runnable {

        @Override
        public void run() {
            while (true) {
                lock.lock();
                try {
                    while (buffer.size() >= MAX) {
                        System.out.println("满了...");
                        try {
                            producer.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if (buffer.size() < MAX) {
                        buffer.add(new Object());
                        System.out.println("生产一个...生产后数量是" + buffer.size());
                        consumer.signalAll();
                        SleepTools.randomMs(5000);
                    }
                } finally {
                    lock.unlock();
                }
                //避免第一次生产者生产之后,一直重复获取到所,导致满了之后才会有消费者开始消费
                SleepTools.randomMs(5);
            }
        }
    }

    static class Consumer implements Runnable {

        @Override
        public void run() {
            while (true) {
                lock.lock();
                try {
                    while (buffer.size() <= 0) {
                        System.out.println("空了...");
                        try {
                            consumer.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if (buffer.size() > 0) {
                        buffer.removeFirst();
                        System.out.println("消费一个...消费后数量是" + buffer.size());
                        producer.signalAll();
                        SleepTools.randomMs(5000);
                    }
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

四、同步队列

  • 代码
public class Pc {

    private static AtomicInteger integer = new AtomicInteger(0);

    public static void main(String[] args) {
        //BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(100);
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(100);
        Producer p = new Producer(blockingQueue);
        Consumer c1 = new Consumer(blockingQueue);
        Consumer c2 = new Consumer(blockingQueue);
        new Thread(p).start();
        new Thread(p).start();
        new Thread(c1).start();
        new Thread(c2).start();
    }


    static class Producer implements Runnable {

        BlockingQueue<Integer> blockingQueue;

        Producer(BlockingQueue blockingQueue) {
            this.blockingQueue = blockingQueue;
        }

        @Override
        public void run() {
            while (true) {
                int i = integer.getAndIncrement();
                blockingQueue.add(i);
                System.out.println("生产者生产商品,编号为" + i);
                SleepTools.randomMs(5000);
            }
        }
    }

    static class Consumer implements Runnable {

        BlockingQueue<Integer> blockingQueue;

        Consumer(BlockingQueue blockingQueue) {
            this.blockingQueue = blockingQueue;
        }

        @Override
        public void run() {
            while (true) {
                Integer element = null;
                try {
                    element = blockingQueue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者消费一个,编号为" + element);
                SleepTools.randomMs(5000);
            }
        }
    }
}

五、有bug的情况

  • 代码

/**
 * @author by mozping
 * @Classname PcCondition
 * @Description TODO
 * 在使用singal的时候会出现这样的问题:
 * 1.某一时刻,生产者生产了之后,商品满了,然后生产者await等待被唤醒,将锁释放;
 * 2.消费者1拿到锁,然后消费掉商品,再调用singal,消费者1将锁释放
 * 3.消费者2被消费者1的singal唤醒,发现没有可消费的商品,然后就await等待自己被生产者唤醒,消费者2释放锁
 * 4.消费者1拿到锁,发现不能消费,也调用await,这样生产者和2个消费者都阻塞了
 *
 * 1.这里的原因在于singal唤醒了同类,即第2步到第3步唤醒了同类导致生产者没有被唤醒,我自己验证用singalAll没有死锁的问题,这回唤醒全部等待在一个condition的线程
 * 2.第二个方法是在await之前发生singal信号,不过最好还是使用singalAll,这样即使自己要阻塞了也会唤醒其他的线程避免像前面的第四步* 那样全部阻塞
 * 3.最推荐的方法是第三个,就是使用2个Condition,生产者和消费者等待在不同的Condition对象,因此不会出现唤醒同理的情况
 *  @Date 2019/8/12 17:36
 */
public class PcConditionBug {

    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();
    private static LinkedList<Object> buffer = new LinkedList<>();
    private static final int MAX = 5;

    public static void main(String[] args) {
        new Thread(new Producer()).start();
        new Thread(new Producer()).start();
        new Thread(new Consumer()).start();
        new Thread(new Consumer()).start();
    }


    static class Producer implements Runnable {

        @Override
        public void run() {
            while (true) {
                lock.lock();
                try {
                    while (buffer.size() >= MAX) {
                        System.out.println("满了...");
                        try {
                            condition.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if (buffer.size() < MAX) {
                        buffer.add(new Object());
                        System.out.println("生产一个...生产后数量是" + buffer.size());
                        condition.signal();
                        SleepTools.randomMs(50);
                    }
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    static class Consumer implements Runnable {

        @Override
        public void run() {
            while (true) {
                lock.lock();
                try {
                    while (buffer.size() <= 0) {
                        System.out.println("空了...");
                        try {
                            condition.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if (buffer.size() > 0) {
                        buffer.removeFirst();
                        System.out.println("消费一个...消费后数量是" + buffer.size());
                        condition.signal();
                        SleepTools.randomMs(50);
                    }
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

对比

方案优点缺点
synchronized代码简单,方法被同步执行,线程安全相对没那么灵活
wait-notify容易学习了解同步机制需要注意死锁的情况,用singal要小心
Condition容易控制注意自由一个Condition的时候有可能会死锁
同步队列不需要考虑线程安全,线程安全容器实现了线程间的同步,编码简单不利于了解同步细节
  • 如果要实现安全高效的,建议使用同步队列,另外双Condition也很好
好的,以下是这几道分布式事务 Seata 相关的面试题的答案: 1. 什么是分布式事务?为什么需要分布式事务? 分布式事务是指涉及到多个不同的数据库或应用系统的事务处理。由于分布式环境下数据的一致性难以保证,因此需要采用分布式事务来确保事务的一致性。 2. Seata 是什么?它有哪些主要功能? Seata 是一款开源的分布式事务解决方案,主要提供了分布式事务的管理和协调功能,包括全局事务管理、分布式事务协调、分布式事务恢复等。Seata 提供了 AT、TCC 和 SAGA 三种分布式事务模式,能够满足不同场景的分布式事务需求。 3. Seata 的分布式事务实现原理是什么? Seata 的分布式事务实现原理是使用了两阶段提交(2PC)协议来实现分布式事务的提交和回滚,同时在 2PC 协议的基础上,引入了 Seata 自己的补偿机制来保证分布式事务的一致性。 4. Seata 支持哪些分布式事务模式? Seata 支持 AT(自动补偿型)、TCC(两阶段型)和 SAGA(补偿型)三种分布式事务模式。 5. Seata 如何保证分布式事务的一致性? Seata 通过 2PC 协议来保证分布式事务的一致性,同时在 2PC 协议的基础上,引入了 Seata 自己的补偿机制来处理分布式事务中的异常情况,确保分布式事务的一致性。 6. Seata 的 AT、TCC 和 SAGA 三种事务模式有什么区别? - AT(自动补偿型):在分布式事务的提交和回滚过程中,Seata 会自动地对操作进行补偿,以保证分布式事务的一致性。 - TCC(两阶段型):在分布式事务的提交和回滚过程中,Seata 会采用两阶段型提交协议来协调各个分支事务,以保证分布式事务的一致性。 - SAGA(补偿型):在分布式事务的提交和回滚过程中,Seata 会借助补偿事务来处理异常情况,确保分布式事务的一致性。 7. Seata 的注册中心和配置中心都有哪些实现方式? Seata 的注册中心和配置中心都支持多种实现方式,包括 Nacos、Eureka、Zookeeper 等。 8. Seata 的使用场景有哪些?它适用于哪些应用场景? Seata 适用于需要处理分布式事务的场景,比如电商订单处理、分布式支付、物流配送等。 9. Seata 在高并发场景下的性能如何? Seata 在高并发场景下的性能表现较好,具体表现取决于使用的分布式事务模式实现方式等因素。 10. Seata 的优缺点是什么?如何选择合适的分布式事务解决方案? Seata 的优点是提供了完善的分布式事务管理和协调功能,支持多种分布式事务模式实现方式。缺点是使用 Seata 需要对业务进行一定的改造,并且可能会影响系统的性能和稳定性。选择合适的分布式事务解决方案需要根据业务场景和需求进行综合考虑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值