Java实现生产者消费者的几种方法

信号量实现(Semaphore)

  • 使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品;只有缓冲区不为空,消费者才可以拿走物品
  • 缓冲区属于临界资源,因此需要使用一个互斥量 mutex来控制对缓冲区的互斥访问。
  • 为了同步生产者和消费者的行为,需要记录缓冲区中物品的数量。数量可以使用信号量来进行统计,这里需要使用两个信号量:empty 记录空缓冲区的数量,full 记录满缓冲区的数量。
public class DemoSemaphore {
    public static void main(String[] args) {
        Item item = new Item();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                item.produce();
            }

        }, "生产者").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                item.consume();
            }

        }, "消费者").start();
    }
}

class Item {
    // 仓库容量
    private final int capacity = 10;
    // 互斥量,只能为 0或 1
    Semaphore mutex = new Semaphore(1);
    // 当 empty 不为 0 才可以放进物品(生产者)
    Semaphore empty = new Semaphore(capacity);
    // 当 full 不为 0 才可以取走物品(消费者)
    Semaphore full = new Semaphore(0);
    // 仓库中商品的数量
    int count = 0;

    public void produce() {
        try {
            empty.acquire();
            mutex.acquire();
            count++;
            System.out.println(Thread.currentThread() + "生产后仓库里的商品数" + count);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            mutex.release();
            full.release();
        }
    }

    public void consume() {
        try {
            full.acquire();
            mutex.acquire();
            count--;
            System.out.println(Thread.currentThread() + "消费后剩余的商品数" + count);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            mutex.release();
            empty.release();
        }
    }
}

注意,不能先对缓冲区进行加锁,再测试信号量。也就是说,不能先执行 mutex.acquire() 再执行 empty.acquire()

如果这么做了,那么可能会出现这种情况:生产者对缓冲区加锁后,执行 empty.acquire() 操作,发现 empty = 0,此时生产者睡眠。消费者不能进入临界区,因为生产者对缓冲区加锁了,消费者就无法执行 empty.release() 操作,empty 永远都为 0,导致生产者永远等待下,不会释放锁,消费者因此也会永远等待下去。

管程实现(Synchronized)

管程是什么?

public class DemoSynchronized {
    public static void main(String[] args) {
        Item item = new Item();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    item.produce();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }, "生产者").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    item.consume();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }, "消费者").start();

    }
}

class Item {
    // 仓库容量
    private final int capacity = 10;
    // 仓库中商品的数量
    int count = 0;

    public synchronized void produce() throws InterruptedException {
        while (count == capacity) {
            wait();
        }
        count++;
        System.out.println(Thread.currentThread() + "生产后仓库里的商品数" + count);
        notify();

    }

    public synchronized void consume() throws InterruptedException {
        while (count == 0) {
            wait();
        }
        count--;
        System.out.println(Thread.currentThread() + "消费后剩余的商品数" + count);
        notify();
    }
}

JUC实现(ReentrantLock + Condition)

public class DemoReentrantLock {
    public static void main(String[] args) {
        Item item = new Item();
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                item.produce();
            }
        }, "生产者").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                item.consume();
            }

        }, "消费者").start();
    }
}

class Item {
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    // 仓库容量
    private final int capacity = 10;
    // 仓库中存放商品的数量
    int count = 0;

    public void produce() {
        lock.lock();
        try {
            while (count == capacity) {
                condition.await();
            }
            count++;
            System.out.println(Thread.currentThread() + "生产后仓库里的商品数" + count);
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void consume() {
        lock.lock();
        try {
            while (count == 0) {
                condition.await();
            }
            count--;
            System.out.println(Thread.currentThread() + "消费后剩余的商品数" +count);
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

BlockingQueue实现

public class DemoBlockingQueue {
    // 相当于仓库容量为 5,仓库中还没有放商品
    private static final BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);
    public static void main(String[] args) {
        new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    queue.put("放入商品");
                    System.out.println(Thread.currentThread() + "生产后仓库里的商品数" + queue.size());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }, "生产者").start();

        new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    queue.take();
                    System.out.println(Thread.currentThread() + "消费后剩余的商品数" + queue.size());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }, "消费者").start();
    }
}
Thread[生产者,5,main]生产后仓库里的商品数1
Thread[消费者,5,main]消费后剩余的商品数0
Thread[生产者,5,main]生产后仓库里的商品数0
Thread[消费者,5,main]消费后剩余的商品数0
Thread[生产者,5,main]生产后仓库里的商品数1
Thread[消费者,5,main]消费后剩余的商品数0
Thread[生产者,5,main]生产后仓库里的商品数1
Thread[消费者,5,main]消费后剩余的商品数0
Thread[生产者,5,main]生产后仓库里的商品数1
Thread[消费者,5,main]消费后剩余的商品数0

输出结果来看不正确,这是因为 put() 或者 take() 操作和 sout 操作之间不是原子的,所以线程是会被抢占,造成查询其容量不正确,但是阻塞队列实现的生产者消费者肯定是线程安全的

Condition实现按序消费

/**
 *要求,A执行完唤醒B,B执行完唤醒C,C执行完唤醒A,顺序执行,不可以乱序
 */
public class DemoCondition {
    public static void main(String[] args) {
        ABC abc = new ABC();
        new Thread(()->{for (int i = 0; i < 10; i++) abc.printA(); },"A").start();
        new Thread(()->{for (int i = 0; i < 10; i++) abc.printB(); },"B").start();
        new Thread(()->{for (int i = 0; i < 10; i++) abc.printC(); },"C").start();
    }

}
class ABC{
    private int num = 1;
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    public void printA(){
        lock.lock();
        try {
            while(num != 1){//线程A等待
                condition1.await();
            }
            num = 2;
            System.out.println(Thread.currentThread().getName()+"=>AAA");
            condition2.signal();//唤醒线程B
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();
        try {
            while(num != 2){
                condition2.await();
            }
            num = 3;
            System.out.println(Thread.currentThread().getName()+"=>BBB");
            condition3.signal();//唤醒线程C
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.lock();
        }
    }
    public void printC(){
        lock.lock();
        try {
            while(num != 3){
                condition3.await();
            }
            num = 1;
            condition1.signal();//唤醒线程A
            System.out.println(Thread.currentThread().getName()+"=>CCC");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.lock();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值