【线程通信】生产者-消费者模式

线程通信的几种方式

一、简述

1️⃣生产者消费者模式并不属于 GOF 提出的23 种设计模式,23 种设计模式都是建立在面向对象的基础之上的。但其实面向过程的编程中也有很多高效的编程模式,生产者消费者模式便是其中之一,它是编程过程中最常用的一种设计模式。

一个常见的场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。单单抽象出生产者和消费者,还够不上是生产者/消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。

2️⃣举个寄信的例子,大致过程如下:

  1. 把信写好——相当于生产者制造数据。
  2. 把信放入邮筒——相当于生产者把数据放入缓冲区。
  3. 邮递员把信从邮筒取出——相当于消费者把数据取出缓冲区。
  4. 邮递员把信拿去邮局做相应的处理——相当于消费者处理数据。

3️⃣说明

  1. 生产消费者模式可以有效的对数据解耦,优化系统结构。
  2. 降低生产者和消费者线程相互之间的依赖与性能要求。
  3. 一般使用 BlockingQueue 作为数据缓冲队列,是通过锁和阻塞来实现数据之间的同步。如果对缓冲队列有性能要求,则可以使用基于CAS无锁设计的ConcurrentLinkedQueue

二、生产者-消费者模式是一个经典的多线程设计模式

【生产者-消费者模式】为多线程间的协作提供了良好的解决方案。在该模式中,通常有两类线程,即若干个生产者线程和若干个消费者线程。生产者线程负责提交用户请求,消费者线程则负责具体处理生产者提交的任务。生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据。阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

Java 中一共有四种方法支持同步,其中前三个是同步方法,一个是管道方法。

  1. Object的 wait()/notify()。
  2. Lock 和 Condition 的 await()/signal()。
  3. BlockingQueue 阻塞队列方法。
  4. Semaphore 信号量
  5. PipedInputStream/PipedOutputStream

三、wait()/notify()

wait()/nofity() 是基类 Object 的两个方法,也就意味着所有 Java 类都有这两个方法,这样就可以为任何对象实现同步机制。

  1. wait():当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等待状态,让其他线程执行。
  2. notify():当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,但不放弃锁。
public class WNPcApp {
    private static final int CAPACITY = 5;

    public static void main(String args[]) {
        Queue<Integer> queue = new LinkedList<Integer>();

        Thread producer1 = new WNProducer("P-1", queue, CAPACITY);
        Thread producer2 = new WNProducer("P-2", queue, CAPACITY);
        Thread consumer1 = new WNConsumer("C1", queue, CAPACITY);
        Thread consumer2 = new WNConsumer("C2", queue, CAPACITY);
        Thread consumer3 = new WNConsumer("C3", queue, CAPACITY);

        producer1.start();
        producer2.start();
        consumer1.start();
        consumer2.start();
        consumer3.start();
    }
}

生产者:

public class WNProducer extends Thread {
    private Queue<Integer> queue;
    String name;
    int maxSize;
    int i = 1;

    public WNProducer(String name, Queue<Integer> queue, int maxSize) {
        super(name);
        this.name = name;
        this.queue = queue;
        this.maxSize = maxSize;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (queue) {
                while (queue.size() == maxSize) {
                    try {
                        System.out.println("Queue is full, Producer[" + name + "] thread waiting for " + "consumer to take something from queue.");
                        queue.wait();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
                System.out.println("[" + name + "] Producing value : +" + i);
                queue.offer(i++);
                queue.notifyAll();

                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

消费者:

public class WNConsumer extends Thread {
    private Queue<Integer> queue;
    String name;
    int maxSize;

    public WNConsumer(String name, Queue<Integer> queue, int maxSize) {
        super(name);
        this.name = name;
        this.queue = queue;
        this.maxSize = maxSize;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (queue) {
                while (queue.isEmpty()) {
                    try {
                        System.out.println("Queue is empty, Consumer[" + name + "] thread is waiting for Producer");
                        queue.wait();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
                int x = queue.poll();
                System.out.println("[" + name + "] Consuming value : " + x);
                queue.notifyAll();

                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

判断 Queue 大小为 0 或者大于等于 queueSize 时须使用while(condition){},不能使用if(condition){}。其中while(condition),它又被叫做自旋锁。为防止该线程没有收到notify()调用也从wait()中返回(也称作虚假唤醒),这个线程会重新去检查 condition 条件以决定当前是否可以安全地继续执行还是需要重新保持等待,而不是认为线程被唤醒了就可以安全地继续执行了。

四、使用Lock和 Condition 的 await()/signal()

JDK5.0之后,Java 提供了更加健壮的线程处理机制,包括同步、锁定、线程池等,它们可以实现更细粒度的线程控制。Condition 接口的 await()/signal() 就是其中用来做同步的两种方法,它们的功能基本上和 Object 的 wait()/nofity() 相同,完全可以取代它们,但是它们和新引入的锁定机制 Lock 直接挂钩,具有更大的灵活性。通过在 Lock 对象上调用newCondition(),将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全。下面来看代码:

public class LockPcApp {
    private static final int CAPACITY = 5;

    static final ReentrantLock lock = new ReentrantLock();
    //条件变量:队列不满
    static final Condition emptyCondition = lock.newCondition();
    //条件变量:队列不空
    static final Condition fullCondition = lock.newCondition();

    public static void main(String args[]) {
        Queue<Integer> queue = new LinkedList<Integer>();

        Thread producer1 = new LCProducer("P-1", queue, CAPACITY, lock, emptyCondition, fullCondition);
        Thread producer2 = new LCProducer("P-2", queue, CAPACITY, lock, emptyCondition, fullCondition);
        Thread consumer1 = new LCConsumer("C1", queue, CAPACITY, lock, emptyCondition, fullCondition);
        Thread consumer2 = new LCConsumer("C2", queue, CAPACITY, lock, emptyCondition, fullCondition);
        Thread consumer3 = new LCConsumer("C3", queue, CAPACITY, lock, emptyCondition, fullCondition);

        producer1.start();
        producer2.start();
        consumer1.start();
        consumer2.start();
        consumer3.start();
    }
}

生产者

public class LCProducer extends Thread {
    private Queue<Integer> queue;
    String name;
    int maxSize;
    int i = 0;

    ReentrantLock lock;
    //条件变量:队列不满
    Condition emptyCondition;
    //条件变量:队列不空
    Condition fullCondition;

    public LCProducer(String name, Queue<Integer> queue, int maxSize, ReentrantLock lock, Condition emptyCondition, Condition fullCondition) {
        super(name);
        this.name = name;
        this.queue = queue;
        this.maxSize = maxSize;
        this.lock = lock;
        //条件变量:队列不满
        this.emptyCondition = emptyCondition;
        //条件变量:队列不空
        this.fullCondition = fullCondition;
    }

    @Override
    public void run() {
        while (true) {

            //获得锁
            lock.lock();
            while (queue.size() == maxSize) {
                try {
                    System.out.println("Queue is full, Producer[" + name + "] thread waiting for " + "consumer to take something from queue.");
                    //条件不满足,生产阻塞
                    fullCondition.await();
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
            System.out.println("[" + name + "] Producing value : +" + i);
            queue.offer(i++);

            //唤醒其他所有生产者、消费者
            fullCondition.signalAll();
            emptyCondition.signalAll();

            //释放锁
            lock.unlock();

            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者

public class LCConsumer extends Thread {
    private Queue<Integer> queue;
    String name;
    int maxSize;
    ReentrantLock lock;
    //条件变量:队列不满
    Condition emptyCondition;
    //条件变量:队列不空
    Condition fullCondition;

    public LCConsumer(String name, Queue<Integer> queue, int maxSize, ReentrantLock lock, Condition emptyCondition, Condition fullCondition) {
        super(name);
        this.name = name;
        this.queue = queue;
        this.maxSize = maxSize;
        this.lock = lock;
        //条件变量:队列不满
        this.emptyCondition = emptyCondition;
        //条件变量:队列不空
        this.fullCondition = fullCondition;
    }

    @Override
    public void run() {
        while (true) {
            //获得锁
            lock.lock();

            while (queue.isEmpty()) {
                try {
                    System.out.println("Queue is empty, Consumer[" + name + "] thread is waiting for Producer");
                    //条件不满足,消费阻塞
                    emptyCondition.await();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            int x = queue.poll();
            System.out.println("[" + name + "] Consuming value : " + x);

            //唤醒其他所有生产者、消费者
            fullCondition.signalAll();
            emptyCondition.signalAll();

            //释放锁
            lock.unlock();

            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

五、使用 BlockingQueue 阻塞队列方法

JDK1.5 以后新增的java.util.concurrent包新增了 BlockingQueue 接口。并提供了如下几种阻塞队列实现:

  1. java.util.concurrent.ArrayBlockingQueue
  2. java.util.concurrent.LinkedBlockingQueue
  3. java.util.concurrent.SynchronousQueue
  4. java.util.concurrent.PriorityBlockingQueue

实现生产者-消费者模型使用 ArrayBlockingQueue 或者 LinkedBlockingQueue 即可。

这里使用 LinkedBlockingQueue,它是一个已经在内部实现了同步的队列,实现方式采用的是 await()/signal()。它可以在生成对象时指定容量大小。它用于阻塞操作的是 put()/take()。

  1. put():类似于上面的生产者线程,容量达到最大时,自动阻塞。
  2. take():类似于上面的消费者线程,容量为 0 时,自动阻塞。

LinkedBlockingQueue 类的 put() 源码:

/** Main lock guarding all access */
final ReentrantLock lock = new ReentrantLock();

/** Condition for waiting takes */
private final Condition notEmpty = lock.newCondition();

/** Condition for waiting puts */
private final Condition notFull = lock.newCondition();

public void put(E e) throws InterruptedException {
    putLast(e);
}

public void putLast(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    Node<E> node = new Node<E>(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        while (!linkLast(node))
            notFull.await();
    } finally {
        lock.unlock();
    }
}

整体逻辑如下:

public class BQPcApp {
    private static final int CAPACITY = 5;

    public static void main(String args[]) {

        LinkedBlockingDeque<Integer> blockingQueue = new LinkedBlockingDeque<Integer>(CAPACITY);

        Thread producer1 = new BQProducer("P-1", blockingQueue, CAPACITY);
        Thread producer2 = new BQProducer("P-2", blockingQueue, CAPACITY);
        Thread consumer1 = new BQConsumer("C1", blockingQueue, CAPACITY);
        Thread consumer2 = new BQConsumer("C2", blockingQueue, CAPACITY);
        Thread consumer3 = new BQConsumer("C3", blockingQueue, CAPACITY);

        producer1.start();
        producer2.start();
        consumer1.start();
        consumer2.start();
        consumer3.start();
    }

}

生产者:

public class BQProducer extends Thread {

    private LinkedBlockingDeque<Integer> blockingQueue;
    String name;
    int maxSize;
    int i = 1;

    public BQProducer(String name, LinkedBlockingDeque<Integer> queue, int maxSize) {
        super(name);
        this.name = name;
        this.blockingQueue = queue;
        this.maxSize = maxSize;
    }

    @Override
    public void run() {
        while (true) {
            try {
                blockingQueue.put(i);
                System.out.println("[" + name + "] Producing value : +" + i);
                i++;
                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者:

public class BQConsumer extends Thread {

    private LinkedBlockingDeque<Integer> blockingQueue;
    String name;
    int maxSize;

    public BQConsumer(String name, LinkedBlockingDeque<Integer> queue, int maxSize) {
        super(name);
        this.name = name;
        this.blockingQueue = queue;
        this.maxSize = maxSize;
    }

    @Override
    public void run() {
        while (true) {
            try {
                int x = blockingQueue.take();
                System.out.println("[" + name + "] Consuming : " + x);
                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

六、Semaphore实现

public class SPcApp {

    List<Integer> buffer = new LinkedList<>();

    // 互斥量,控制buffer的互斥访问
    private Semaphore mutex = new Semaphore(1);

    // canProduceCount可以执行生产的数量。 通过生产者调用acquire,减少permit数目
    private Semaphore sePro = new Semaphore(10);

    // canConsumerCount可以执行消费的数量。通过生产者调用release,增加permit数目
    private Semaphore seCon = new Semaphore(0);
    Random rn = new Random(10);

    public void put() throws InterruptedException {
        sePro.acquire();
        try {
            mutex.acquire();
            int val = rn.nextInt(10);
            buffer.add(val);
            System.out.println(Thread.currentThread().getName() + " 生产了数据【" + val + "】buffer目前大小为:" + buffer.size());
        } finally {
            mutex.release();
            // 生产者调用release,增加可以消费的数量
            seCon.release();
        }
    }

    public void get() throws InterruptedException {
        seCon.acquire();
        try {
            mutex.acquire();
            int val = buffer.remove(0);
            System.out.println(Thread.currentThread().getName() + "~~~消费了数据【" + val + "】buffer目前大小为:" + buffer.size());
            Thread.sleep(2000);
        } finally {
            mutex.release();
            sePro.release();
        }
    }

    public static void main(String[] args) {
        final SPcApp sPcApp = new SPcApp();
        new Thread(new SProducer(sPcApp)).start();
        new Thread(new SProducer(sPcApp)).start();
        new Thread(new SConsumer(sPcApp)).start();
        new Thread(new SConsumer(sPcApp)).start();
    }
}

生产者:

public class SProducer implements Runnable {

    private SPcApp sPcApp;

    public SProducer(SPcApp sPcApp) {
        this.sPcApp = sPcApp;
    }

    @Override
    public void run() {
        try {
            while (true) {
                sPcApp.put();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

消费者:

public class SConsumer implements Runnable {

    private SPcApp sPcApp;

    public SConsumer(SPcApp sPcApp) {
        this.sPcApp = sPcApp;
    }

    @Override
    public void run() {
        try {
            while (true) {
                sPcApp.get();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JFS_Study

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值