java多线程-经典生产者消费者问题

问题:

写一个固定容量同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及5个消费者线程的阻塞调用。

方案一:

1.使用synchronized、notifyAll、wait实现

public class PSproblem_01<T> {
    final private LinkedList<T> lists = new LinkedList<>();
    final private int MAX = 10; //最多10个元素
    //生产过程
    public synchronized void put(T t) {
        while (lists.size() == MAX) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        lists.add(t);
        System.out.println("生产者" + Thread.currentThread().getName() + "生产了" + t);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.notifyAll();
    }
	//消费过程
    public synchronized T get() {
        T t = null;
        while (lists.size() == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        t = lists.remove();
        System.out.println("消费者" + Thread.currentThread().getName() + "消费了" + t);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.notifyAll();
        return t;
    }


    public static void main(String[] args) {
        PSproblem_01<String> container = new PSproblem_01<>();

        //启动2个生产者线程
        for (int i = 1; i <= 2; i++) {
            int index = i;
            new Thread(() -> {
                int count = 0;
                while (count++ < 5) {
                    container.put("包子");
                }
            }, Integer.toString(index)).start();
        }
        //启动5个消费者线程
        for (int i = 1; i <= 5; i++) {
            int index = i;
            new Thread(() -> {
                int count = 0;
                while (count++ < 2) {
                    container.get();
                }
            }, Integer.toString(index)).start();
        }
    }
}

3.疑问
为什么判断条件用while而不用if?
先来了解一下两个概念,以便更容易理解问题答案。

  • 锁池:
    假设线程A已经拥有了某个对象的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
  • 等待池:
    假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入 到了该对象的等待池中。
  • 当有线程调用了对象的notifyAll()方法(唤醒所有wait线程)或notify()方法(只随机唤醒一个wait线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争

假设我们遇到这样情况:
生产者线程1获取锁后,容器容量判断已经到达MAX,所以线程1执行wait()后进入释放当前对像锁,进入等待池中。随后又一个生产者线程2获得锁,由于容量还是MAX,所以线程2也释放锁进入等待池。后面消费者线程3消费了一次,执行notifyAll唤醒了当前对象的所有等待池的线程。这个时候线程1抢到了锁,进行一次生产,但是可能下次抢到锁的是线程2,这时线程2上次wait之后的代码将继续执行下去,进行了一次生产,但是此时容器容量已经是11个了。如果我们用while的话,线程3这次会重新判定一次容量是否满了,这样满了又会进入等待池,直到后面满足条件才进行生产。
所以我们这里必须用while,而不能用if。

方案二(推荐):使用Condition

使用Lock和Condition来实现, 对比两种方式,Condition的方式可以更加精确的指定哪些线程被唤醒。

代码:

public class PSproblem<T> {

    final private LinkedList<T> lists = new LinkedList<>();
    final private int MAX = 10; //最多10个元素
    ReentrantLock lock = new ReentrantLock();
    Condition producer = lock.newCondition();//用于生产线程
    Condition consumer = lock.newCondition();//用于消费线程
    //消费过程
    public T get() {
        T t = null;
        try {
            lock.lock();
            while (lists.size() == 0) {
                try {
                    consumer.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //模拟消费
            t = lists.removeFirst();
            System.out.println("消费者" + Thread.currentThread().getName() + "消费产品");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            producer.signalAll();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return t;
    }
	//生产过程
    public void put(T t) {
        try {
            lock.lock();
            while (lists.size() == MAX) {
                try {
                    producer.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //模拟生产
            lists.add(t);
            System.out.println("生产者" + Thread.currentThread().getName() + "生产了产品");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            consumer.signalAll();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
       PSproblem<String> container = new PSproblem<>();
        //启动2个生产者线程
        for (int i = 1; i <= 2; i++) {
            int index = i;
            new Thread(() -> {
                int count = 0;
                while (count++ < 5) {
                    container.put("包子");
                }
            }, Integer.toString(index)).start();
        }
        //启动5个消费者线程
        for (int i = 1; i <= 5; i++) {
            int index = i;
            new Thread(() -> {
                int count = 0;
                while (count++ < 2) {
                    container.get();
                }
            }, Integer.toString(index)).start();
        }
    }
}

结果

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值