手撕生产者-消费者模式 | P问题、NP问题

本文详细介绍了如何使用Java的BlockingQueue接口实现生产者-消费者模式,包括wait/notify、Lock的Condition以及具体实现类如ArrayBlockingQueue的使用。此外,还探讨了P问题和NP问题的理论背景,分析了两者的关系及其在算法复杂度中的重要意义。

手撕BlockingQueue

public class RequestQueue {
    private final Queue<Request> queue = new LinkedList<>();
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public  Request getRequest() {
        try {
            lock.lock();
            while (queue.peek() == null) {
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return queue.remove();
        } finally {
            lock.unlock();
        }
    }
    public void putRequest(Request request) {
        try {
            lock.lock();
            queue.offer(request);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

生产者-消费者模式

在实现生产者消费者问题时,可以采用三种方式:
1.使用Object的wait/notify的消息通知机制
2.使用Lock的Condition的await/signal的消息通知机制
3.使用BlockingQueue实现

一.wait/notify实现生产者和消费者模式

public class ProductAndConsumer{
    /*
    1.生产者不断的向里面加东西
    2.消费者不断地取东西
    3.生产者发现容器里满了就wait
    4.消费者发现容器里没东西了就wait
    5.生产者生产东西唤醒消费者
    6.消费者消费东西唤醒生产者
    7.锁对象要是同一个对象才行*/
    public static void main(String[] args){
        ProductAndConsumer ps = new ProductAndConsumer();
        Resources resources = ps.new Resources(10,new LinkedList<Product>());
        new Thread(ps.new Producer(resources)).start();
        new Thread(ps.new Consumer(resources)).start();
    }
    class Product{
        String name;
        public Product(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
    }
    //生产者
    class Producer implements Runnable{
        Resources resources;
        @Override
        public void run() {
            while (true){
                String name = (System.currentTimeMillis()+">"+(int)(Math.random()*100))+"号产品";
                System.out.println("生产者生产了:"+name);
                resources.put(new Product(name));
            }
        }
        public Producer(Resources resources) {
            this.resources = resources;
        }
    }
    //消费者
    class Consumer implements Runnable{
        Resources resources;
        @Override
        public void run() {
            while (true){
                Product product = resources.pop();
                System.out.println("消费者消费了:"+product.name);
            }
        }
        public Consumer(Resources resources) {
            this.resources = resources;
        }
    }
    class Resources{
        int maxLength;
        private List<Product> productList;
        synchronized void put(Product product){
            while (productList.size()==maxLength){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            productList.add(product);
            this.notifyAll();
        }
        synchronized Product pop(){
            while(productList.isEmpty()){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Product product = productList.remove(0);
            this.notifyAll();
            return product;
        }
        public Resources(int maxLength, List<Product> productList) {
            this.maxLength = maxLength;
            this.productList = productList;
        }
    }
}

二.使用Lock的Condition的await和signal的方式
  Lock用于控制多线程对同一状态的顺序访问,保证该状态的连续性。Condition用于控制多线程之间的、基于该状态的条件等待。

重点:
  Condition它更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。

public class ProductAndConsumerLock {
    class Resources{
        int maxLength;
        private List<Product> productList;
        ReentrantLock lock;
        Condition full;
        Condition empty;
        void put(Product product){
            lock.lock();
            while (productList.size()==maxLength){
                try {
                    full.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            productList.add(product);
            empty.signalAll();
            lock.unlock();
        }
        Product pop(){
            lock.lock();
            while(productList.isEmpty()){
                try {
                    empty.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Product product = productList.remove(0);
            full.signalAll();
            lock.unlock();
            return product;
        }
        public Resources(int maxLength, List<Product> productList, ReentrantLock lock, Condition full, Condition empty) {
            this.maxLength = maxLength;
            this.productList = productList;
            this.lock = lock;
            this.full = full;
            this.empty = empty;
        }
    }
    public static void main(String[] args) {
        ProductAndConsumerLock ps = new ProductAndConsumerLock();
        ReentrantLock lock = new ReentrantLock();
        Condition full = lock.newCondition();
        Condition empty = lock.newCondition();
        Resources resources = ps.new Resources(10, new LinkedList<Product>(), lock, full,empty);
        new Thread(ps.new Producer(resources)).start();
        new Thread(ps.new Consumer(resources)).start();
    }
}

三.使用BlockingQuene实现
  BlockingQueue即阻塞队列,它是基于ReentrantLock,依据它的基本原理,我们可以实现生产者与消费者模式,大致如下图所示:
在这里插入图片描述
  在Java中,BlockingQueue是一个接口,它的实现类有ArrayBlockingQueue、DelayQueue、 LinkedBlockingDeque、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等,它们的区别主要体现在存储结构上或对元素操作上的不同,但是对于take与put操作的原理,却是类似的。

BlockingQueue的入队和出队的方法有很多,下面是各种方法的区别
1.入队
offer(E e):如果队列没满,立即返回true; 如果队列满了,立即返回false–>不阻塞
put(E e):如果队列满了,一直阻塞,直到队列不满了或者线程被中断–>阻塞
offer(E e, long timeout, TimeUnit unit):在队尾插入一个元素,,如果队列已满,则进入等待。

2.出队
poll():如果没有元素,直接返回null;如果有元素,出队
take():如果队列空了,一直阻塞,直到队列不为空或者线程被中断–>阻塞
poll(long timeout, TimeUnit unit):如果队列不空,出队;如果队列已空且已经超时,返回null;如果队列已空且时间未超时,则进入等待。

在这里,我们使用put和take的方式实现,生产者消费者模式
生产者类:

import java.util.concurrent.BlockingQueue;

public class Producer implements Runnable {
    BlockingQueue<String> queue;
    public Producer(BlockingQueue<String> queue){
        this.queue=queue;
    }

    @Override
    public void run() {
        try {
            String temp="消费线程"+Thread.currentThread().getName();
            System.out.println("生产线程"+Thread.currentThread().getName());
            queue.put(temp);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

消费者类:

import java.util.concurrent.BlockingQueue;

public class Customer implements Runnable {
    BlockingQueue<String> queue;
    public Customer(BlockingQueue<String> queue){
        this.queue=queue;
    }


    @Override
    public void run() {
        try {
            String temp=queue.take();
            System.out.println(temp);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

测试类:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class Test {
    public static void main(String[] args) {
        BlockingQueue<String> queue=new LinkedBlockingQueue<String>(2);
        Producer producer=new Producer(queue);
        Customer customer=new Customer(queue);
        for(int i=0;i<5;i++){
            new Thread(producer,"Producer"+(i+1)).start();
            new Thread(customer,"Customer"+(i+1)).start();
        }
    }
}

P问题、NP问题

研究P/NP问题的原因:
  因为计算机处理的输入常常不是那么几十个几千个那么一点点,想象一下,当计算机处理的数据达到100万个的时候,时间复杂度为o(n^ 2)和o(e^ n)的算法,所需的运行次数简直是天壤之别,o(e^n)指数级的可能运行好几天都没法完成任务,所以我们才要研究一个问题是否存在多项式时间算法。而我们也只在乎一个问题是否存在多项式算法,因为一个时间复杂度比多项式算法还要复杂的算法研究起来是没有任何实际意义的。

P类问题:存在多项式时间算法的问题。(P:polynominal,多项式)
NP类问题:能在多项式时间内验证得出一个正确解的问题。(NP:Nondeterministic polynominal,非确定性多项式)

P类问题是NP问题的子集,因为存在多项式时间解法的问题,总能在多项式时间内验证它。NP类问题,不知道这个问题是不是存在多项式时间内的算法,所以叫non-deterministic非确定性,但是可以在多项式时间内验证并得出这个问题的一个正确解。举个例子,

  著名的NP类问题:旅行家推销问题(TSP)。即有一个推销员,要到n个城市推销商品,他要找出一个包含所有n个城市的环路,这个环路路径小于a。我们知道这个问题如果单纯的用枚举法来列举的话会有(n-1)! 种,已经不是多项式时间的算法了,(注:阶乘算法比多项式的复杂)。那怎么办呢?我们可以用猜的,假设我人品好,猜几次就猜中了一条小于长度a的路径,我画画画画,好的,我得到了一条路径小于a的环路,问题解决了,皆大欢喜。可是,我不可能每次都猜的那么准,也许我要猜完所有种呢?所以我们说,这是一个NP类问题。也就是,我们能在多项式的时间内验证并得出问题的正确解,可是我们却不知道该问题是否存在一个多项式时间的算法,每次都能解决他(注意,这里是不知道,不是不存在)。

  因为计算机处理的输入常常不是那么几十个几千个那么一点点,想象一下,当计算机处理的数据达到100万个的时候,时间复杂度为o(n^ 2)和o(e^ n)的算法,所需的运行次数简直是天壤之别,o(e^n)指数级的可能运行好几天都没法完成任务,所以我们才要研究一个问题是否存在多项式时间算法。而我们也只在乎一个问题是否存在多项式算法,因为一个时间复杂度比多项式算法还要复杂的算法研究起来是没有任何实际意义的。所以这就引出了这类讨论的一个千年问题:是否 NP类问题=P类问题?即,是否所有能在多项式时间内验证得出正确解的问题,都是具有多项式时间算法的问题呢?要是解决了这个问题,那岂不是所有的NP问题都可以通过计算机来解决?有的存在,有的不存在。

  因为计算机处理的输入常常不是那么几十个几千个那么一点点,想象一下,当计算机处理的数据达到100万个的时候,时间复杂度为o(n^ 2)和o(e^ n)的算法,所需的运行次数简直是天壤之别,o(e^n)指数级的可能运行好几天都没法完成任务,所以我们才要研究一个问题是否存在多项式时间算法。而我们也只在乎一个问题是否存在多项式算法,因为一个时间复杂度比多项式算法还要复杂的算法研究起来是没有任何实际意义的。为了证明这个千古难题,科学家想出了很多办法。其中之一就是问题的约化。所谓问题约化就是,可以用问题B的算法来解决A ,我们就说问题A可以约化成问题B。举个例子,一元一次方程的求解,跟二元一次方程的求解,我们知道,只要能求解二元一次方程,那就可以用二元一次方程的解法来求解一元一次方程,只需要将一元一次方程加上y,并附加一个方程y=0就可以将一元一次方程变形为一个二元一次方程,然后用二元一次方程的解法来求解这个方程。注意,这里二元一次方程的解法会比一元一次的复杂。所以我们说,只需要找到解二元一次方程的规则性解法,那就能用这个规则性解法来求解一元一次方程。从这里也可以看出,约化是具有传递性的,如A约化到B,B约化到C,A就可以约化到C,同时不断约化下去,我们会发现一个很惊人的特性,就是他一定会存在一个最大的问题,而我们只需要解决了这个问题,那其下的所有问题也就解决啦,即NPC问题的概念。
  引到NP问题里就是,对于同一类的所有的NP类问题,若他们都可以在多项式时间内约化成最难的一个NP类问题,(我们直观的认为,被约化成的问题应具有比前一个问题更复杂的时间复杂度)当我们针对这个时间复杂度最高的超级NP问题要是能找到他的多项式时间算法的话,那就等于变向的证明了其下的所有问题都是存在多项式算法的,即NP=P。给出NPC问题定义。

NPC类问题(Nondeterminism Polynomial complete)
  存在这样一个NP问题,所有的NP问题都可以约化成它。换句话说,只要解决了这个问题,那么所有的NP问题都解决了。其定义要满足2个条件: 首先,它得是一个NP问题;然后,所有的NP问题都可以约化到它。要证明npc问题的思路就是: 先证明它至少是一个NP问题,再证明其中一个已知的NPC问题能约化到它。

NP难问题(NP-hard问题)
  NP-Hard问题是这样一种问题,它满足NPC问题定义的第二条但不一定要满足第一条(就是说,NP-Hard问题要比 NPC问题的范围广,NP-Hard问题没有限定属于NP),即所有的NP问题都能约化到它,但是他不一定是一个NP问题。
   NP-Hard问题同样难以找到多项式的算法,但它不列入我们的研究范围,因为它不一定是NP问题。即使NPC问题发现了多项式级的算法,NP-Hard问题有可能仍然无法得到多项式级的算法。事实上,由于NP-Hard放宽了限定条件,它将有可能比所有的NPC问题的时间复杂度更高从而更难以解决。

以上四个问题它们之间的关系可以用下图来表示:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值