Java四种方式解决生产者消费者问题

         生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个存储空间,生产者向空间里存放数据,而消费者取用数据,如果不加以协调可能会出现以下情况:存储空间已满,而生产者占用着它,消费者等着生产者让出空间从而去除产品,生产者等着消费者消费产品,从而向空间中添加产品。互相等待,从而发生死锁。

一、wait() / notify()方法

        Java 中,可以通过配合调用 Object 对象的 wait() 方法和 notify()方法或 notifyAll() 方法来实现线程间的通信。在线程中调用 wait() 方法,将阻塞当前线程,直至等到其他线程调用了 notify() 方法或 notifyAll() 方法进行通知之后,当前线程才能从wait()方法出返回,继续执行下面的操作。

package com.study.thread;

import java.util.LinkedList;

class ShareDataSync {
    private int maxNum = 10;
    private LinkedList<String> list = new LinkedList<>();

    //生产者
    public void produceData() {
        synchronized (list) {
            try {
                while (list.size() >= maxNum) {
                    //仓库已满,等待被消费
                    list.wait();
                }
                list.add("1");
                System.out.println(Thread.currentThread().getName() + "生产者,剩余库存=" + list.size());
                list.notifyAll();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //消费者
    public void consumeData() {
        synchronized (list) {
            try {
                while (list.size() <= 0) {
                    //等待数据被生产
                    list.wait();
                }
                list.remove(0);
                System.out.println(Thread.currentThread().getName() + "消费者,剩余库存=" + list.size());
                list.notifyAll();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

public class ProductConsumeSynchorizeDemo {
    public static void main(String[] args) {
        ShareDataSync shareData = new ShareDataSync();
        //生产
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                shareData.produceData();
            }
        }, "AA").start();
        //消费
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                shareData.consumeData();
            }
        }, "BB").start();
        //生产
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                shareData.produceData();
            }
        }, "CC").start();
        //消费
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                shareData.consumeData();
            }
        }, "DD").start();
    }
}

运行结果:

AA生产者,剩余库存=1
AA生产者,剩余库存=2
AA生产者,剩余库存=3
AA生产者,剩余库存=4
AA生产者,剩余库存=5
BB消费者,剩余库存=4
BB消费者,剩余库存=3
BB消费者,剩余库存=2
BB消费者,剩余库存=1
BB消费者,剩余库存=0
CC生产者,剩余库存=1
CC生产者,剩余库存=2
CC生产者,剩余库存=3
CC生产者,剩余库存=4
CC生产者,剩余库存=5
DD消费者,剩余库存=4
DD消费者,剩余库存=3
DD消费者,剩余库存=2
DD消费者,剩余库存=1
DD消费者,剩余库存=0

 注意:

notifyAll()方法可使所有正在等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。此时,优先级最高的哪个线程最先执行,但也有可能是随机执行的,这要取决于JVM虚拟机的实现。即最终也只有一个线程能被运行,上述线程优先级都相同,每次运行的线程都不确定是哪个,后来给线程设置优先级后也跟预期不一样,还是要看JVM的具体实现吧。

二、await() / signal()方法

在JDK5中,用ReentrantLock和Condition可以实现等待/通知模型,具有更大的灵活性。通过在Lock对象上调用newCondition()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全。

package com.study.thread;

import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class ShareData {
    private int maxNum = 10;
    private LinkedList<String> list = new LinkedList<>();
    Lock lock = new ReentrantLock();
    Condition full = lock.newCondition();
    Condition empty = lock.newCondition();

    //生产者
    public void produceData() {
        lock.lock();
        try {
            while (list.size() >= maxNum) {
                //仓库已满,等待被消费
                full.await();
            }
            list.add("1");
            System.out.println(Thread.currentThread().getName() + "生产者,剩余库存=" + list.size());
            empty.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //消费者
    public void consumeData() {
        lock.lock();
        try {
            while (list.size() <= 0) {
                //等待数据被生产
                empty.await();
            }
            list.remove(0);
            System.out.println(Thread.currentThread().getName() + "消费者,剩余库存=" + list.size());
            full.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class ProductConsumeLockDemo {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();
        //生产
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                shareData.produceData();
            }

        }, "AA").start();
        //消费
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                shareData.consumeData();
            }
        }, "DD").start();
        //消费
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                shareData.consumeData();
            }
        }, "BB").start();
        //生产
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                shareData.produceData();
            }
        }, "CC").start();
    }
}

运行结果:

AA生产者,剩余库存=1
AA生产者,剩余库存=2
CC生产者,剩余库存=3
CC生产者,剩余库存=4
CC生产者,剩余库存=5
CC生产者,剩余库存=6
CC生产者,剩余库存=7
DD消费者,剩余库存=6
DD消费者,剩余库存=5
DD消费者,剩余库存=4
DD消费者,剩余库存=3
DD消费者,剩余库存=2
BB消费者,剩余库存=1
BB消费者,剩余库存=0
AA生产者,剩余库存=1
AA生产者,剩余库存=2
AA生产者,剩余库存=3
BB消费者,剩余库存=2
BB消费者,剩余库存=1
BB消费者,剩余库存=0

 

三、BlockingQueue阻塞队列方法

        BlockingQueue是JDK5.0的新增内容,它是一个已经在内部实现了同步的队列,底层实现方式采用的是我们第2种await() / signal()方法。它可以在生成对象时指定容量大小,用于阻塞操作的是put()、offer()和take()、poll()方法。 

package com.study.thread;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

class ShareDataBlockQueue {
    private boolean flag = true;
    //保证多线程下生产者的线程安全
    private AtomicInteger atomicInteger = new AtomicInteger(0);
    BlockingQueue blockingQueue = null;

    public ShareDataBlockQueue(BlockingQueue blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    //生产者
    public void produceData() throws InterruptedException {
        while (flag) {
            int i = atomicInteger.incrementAndGet();
            boolean offer = blockingQueue.offer(i, 3, TimeUnit.SECONDS);
            if (offer) {
                System.out.println(Thread.currentThread().getName() + "生产" + i);
            } else {
                System.out.println(Thread.currentThread().getName() + "生产失败" + i);
            }
            Thread.sleep(1000);
        }
    }

    //消费者
    public void consumeData() throws InterruptedException {
        while (flag) {
            Object poll = blockingQueue.poll(2, TimeUnit.SECONDS);
            if (poll == null || poll.equals("")) {
                flag = false;
                System.out.println("超过2秒没有取到数据,消费退出");
                return;
            } else {
                System.out.println(Thread.currentThread().getName() + "消费" + poll);
            }
        }
    }

    public void stop() {
        this.flag = false;
    }
}

public class ProductConsumeBlockQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue blockingQueue = new ArrayBlockingQueue(10);
        ShareDataBlockQueue shareData = new ShareDataBlockQueue(blockingQueue);
        //生产
        new Thread(() -> {
            try {
                shareData.produceData();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "AA").start();
        new Thread(() -> {
            try {
                shareData.produceData();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "BB").start();
        //消费
        new Thread(() -> {
            try {
                shareData.consumeData();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "CC").start();
        //消费
        new Thread(() -> {
            try {
                shareData.consumeData();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "DD").start();

        Thread.sleep(5000);
        System.out.println("mian进程10s后停止生产,队列大小:" + shareData.blockingQueue.size());
        shareData.stop();
    }
}

 运行结果:

AA生产1
CC消费1
CC消费2
BB生产2
BB生产3
DD消费4
CC消费3
AA生产4
AA生产5
CC消费6
BB生产6
DD消费5
AA生产7
DD消费8
BB生产8
CC消费7
AA生产9
CC消费10
BB生产10
DD消费9
mian进程10s后停止生产,队列大小:0
超过2秒没有取到数据,消费退出
超过2秒没有取到数据,消费退出

四、信号量

        Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。计数为0的Semaphore是可以release的,release之后加1,然后就可以acquire,acquire之后减1

package com.study.thread;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

class ShareDataSemaphore {
    //可以生产的总数量。 通过生产者调用acquire,减少permit数目
    Semaphore canProduceCount = new Semaphore(10);
    //互斥锁 互斥量,控制共享数据的互斥访问
    Semaphore block = new Semaphore(1);
    //可以消费的数量。通过生产者调用release,增加permit数目
    Semaphore canConsumerCount = new Semaphore(0);
    private LinkedList list = new LinkedList<>();

    //生产者
    public void produceData() throws InterruptedException {
        try {
            // 可生产数量 -1
            canProduceCount.acquire();
            //加锁
            block.acquire();
            list.add("1");
            System.out.println(Thread.currentThread().getName() + "生产者,仓库大小" + list.size());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放锁
            block.release();
            //能消费的数量 +1
            canConsumerCount.release();
        }

    }

    //消费者
    public void consumeData() throws InterruptedException {
        try {
            //能消费的数量 -1
            canConsumerCount.acquire();
            //加锁
            block.acquire();
            list.remove();
            System.out.println(Thread.currentThread().getName() + "消费者,仓库大小" + list.size());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            block.release();
            //能生产的数量 +1
            canProduceCount.release();
        }
    }
}

public class ProductConsumeSemaphoreDemo {
    public static void main(String[] args) throws InterruptedException {
        ShareDataSemaphore shareData = new ShareDataSemaphore();
        //生产
        new Thread(() -> {
            try {
                shareData.produceData();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "AA").start();
        new Thread(() -> {
            try {
                shareData.produceData();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "BB").start();
        //消费
        new Thread(() -> {
            try {
                shareData.consumeData();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "CC").start();
        //消费
        new Thread(() -> {
            try {
                shareData.consumeData();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "DD").start();

        // Thread.sleep(5000);
        // shareData.stop();
    }
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值