【Java】生产者消费者模式的三种实现

原文地址:https://blog.csdn.net/u010983881/article/details/78554671

前言

生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据。

阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

wait/notify方法

首先,我们搞清楚Thread.sleep()方法和Object.wait()、Object.notify()方法的区别。根据这篇文章java sleep和wait的区别的疑惑?

  1. sleep()是Thread类的方法;而wait()notify()notifyAll()是Object类中定义的方法;尽管这两个方法都会影响线程的执行行为,但是本质上是有区别的。

  2. Thread.sleep()不会导致锁行为的改变,如果当前线程是拥有锁的,那么Thread.sleep()不会让线程释放锁。如果能够帮助你记忆的话,可以简单认为和锁相关的方法都定义在Object类中,因此调用Thread.sleep()是不会影响锁的相关行为。

  3. Thread.sleepObject.wait都会暂停当前的线程,对于CPU资源来说,不管是哪种方式暂停的线程,都表示它暂时不再需要CPU的执行时间。OS会将执行时间分配给其它线程。区别是调用wait后,需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间。

线程状态图:

  • Thread.sleep()让线程从 【running】 -> 【阻塞态】 时间结束/interrupt -> 【runnable】
  • Object.wait()让线程从 【running】 -> 【等待队列】notify -> 【锁池】 -> 【runnable】

实现生产者消费者模型

生产者消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品。在Java中一共有四种方法支持同步,其中前三个是同步方法,一个是管道方法。

(1)Object的wait() / notify()方法 
(2)LockCondition的await() / signal()方法 
(3)BlockingQueue阻塞队列方法 
(4)PipedInputStream / PipedOutputStream

本文只介绍最常用的前三种,第四种暂不做讨论。源代码在这里:Java实现生产者消费者模型

1. 使用Object的wait() / notify()方法

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

  • wait():当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等待状态,让其他线程执行。
  • notify():当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。
 
  1. /**

  2. * 生产者消费者模式:使用Object.wait() / notify()方法实现

  3. */

  4. public class ProducerConsumer {

  5. private static final int CAPACITY = 5;

  6.  
  7. public static void main(String args[]){

  8. Queue<Integer> queue = new LinkedList<Integer>();

  9.  
  10. Thread producer1 = new Producer("P-1", queue, CAPACITY);

  11. Thread producer2 = new Producer("P-2", queue, CAPACITY);

  12. Thread consumer1 = new Consumer("C1", queue, CAPACITY);

  13. Thread consumer2 = new Consumer("C2", queue, CAPACITY);

  14. Thread consumer3 = new Consumer("C3", queue, CAPACITY);

  15.  
  16. producer1.start();

  17. producer2.start();

  18. consumer1.start();

  19. consumer2.start();

  20. consumer3.start();

  21. }

  22.  
  23. /**

  24. * 生产者

  25. */

  26. public static class Producer extends Thread{

  27. private Queue<Integer> queue;

  28. String name;

  29. int maxSize;

  30. int i = 0;

  31.  
  32. public Producer(String name, Queue<Integer> queue, int maxSize){

  33. super(name);

  34. this.name = name;

  35. this.queue = queue;

  36. this.maxSize = maxSize;

  37. }

  38.  
  39. @Override

  40. public void run(){

  41. while(true){

  42. synchronized(queue){

  43. while(queue.size() == maxSize){

  44. try {

  45. System.out .println("Queue is full, Producer[" + name + "] thread waiting for " + "consumer to take something from queue.");

  46. queue.wait();

  47. } catch (Exception ex) {

  48. ex.printStackTrace();

  49. }

  50. }

  51. System.out.println("[" + name + "] Producing value : +" + i);

  52. queue.offer(i++);

  53. queue.notifyAll();

  54.  
  55. try {

  56. Thread.sleep(new Random().nextInt(1000));

  57. } catch (InterruptedException e) {

  58. e.printStackTrace();

  59. }

  60. }

  61. }

  62.  
  63. }

  64. }

  65.  
  66. /**

  67. * 消费者

  68. */

  69. public static class Consumer extends Thread{

  70. private Queue<Integer> queue;

  71. String name;

  72. int maxSize;

  73.  
  74. public Consumer(String name, Queue<Integer> queue, int maxSize){

  75. super(name);

  76. this.name = name;

  77. this.queue = queue;

  78. this.maxSize = maxSize;

  79. }

  80.  
  81. @Override

  82. public void run(){

  83. while(true){

  84. synchronized(queue){

  85. while(queue.isEmpty()){

  86. try {

  87. System.out.println("Queue is empty, Consumer[" + name + "] thread is waiting for Producer");

  88. queue.wait();

  89. } catch (Exception ex) {

  90. ex.printStackTrace();

  91. }

  92. }

  93. int x = queue.poll();

  94. System.out.println("[" + name + "] Consuming value : " + x);

  95. queue.notifyAll();

  96.  
  97. try {

  98. Thread.sleep(new Random().nextInt(1000));

  99. } catch (InterruptedException e) {

  100. e.printStackTrace();

  101. }

  102. }

  103. }

  104. }

  105. }

  106. }

注意要点

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

输出日志如下:

 
  1. [P-1] Producing value : +0

  2. [P-1] Producing value : +1

  3. [P-1] Producing value : +2

  4. [P-1] Producing value : +3

  5. [P-1] Producing value : +4

  6. Queue is full, Producer[P-1] thread waiting for consumer to take something from queue.

  7. [C3] Consuming value : 0

  8. [C3] Consuming value : 1

  9. [C3] Consuming value : 2

  10. [C3] Consuming value : 3

  11. [C3] Consuming value : 4

  12. Queue is empty, Consumer[C3] thread is waiting for Producer

  13. Queue is empty, Consumer[C2] thread is waiting for Producer

  14. Queue is empty, Consumer[C1] thread is waiting for Producer

  15. [P-2] Producing value : +0

  16. [C1] Consuming value : 0

  17. Queue is empty, Consumer[C1] thread is waiting for Producer

  18. Queue is empty, Consumer[C2] thread is waiting for Producer

  19. Queue is empty, Consumer[C3] thread is waiting for Producer

  20. [P-1] Producing value : +5

  21. [P-1] Producing value : +6

  22. [P-1] Producing value : +7

  23. [P-1] Producing value : +8

  24. [P-1] Producing value : +9

  25. Queue is full, Producer[P-1] thread waiting for consumer to take something from queue.

  26. [C3] Consuming value : 5

  27. [C3] Consuming value : 6

  28. [C3] Consuming value : 7

  29. [C3] Consuming value : 8

  30. [C3] Consuming value : 9

  31. Queue is empty, Consumer[C3] thread is waiting for Producer

  32. Queue is empty, Consumer[C2] thread is waiting for Producer

  33. Queue is empty, Consumer[C1] thread is waiting for Producer

  34. [P-2] Producing value : +1

  35. [C1] Consuming value : 1

  36. Queue is empty, Consumer[C1] thread is waiting for Producer

  37. Queue is empty, Consumer[C2] thread is waiting for Producer

  38. Queue is empty, Consumer[C3] thread is waiting for Producer

  39. [P-1] Producing value : +10

  40. [P-1] Producing value : +11

  41. [P-1] Producing value : +12

  42. [P-1] Producing value : +13

  43. [P-1] Producing value : +14

  44. Queue is full, Producer[P-1] thread waiting for consumer to take something from queue.

  45. [C3] Consuming value : 10

  46. [C3] Consuming value : 11

  47. [C3] Consuming value : 12

  48. [C3] Consuming value : 13

  49. [C3] Consuming value : 14

  50. Queue is empty, Consumer[C3] thread is waiting for Producer

  51. Queue is empty, Consumer[C2] thread is waiting for Producer

  52. Queue is empty, Consumer[C1] thread is waiting for Producer

  53. [P-2] Producing value : +2

  54. [P-2] Producing value : +3

  55. [P-2] Producing value : +4

  56. [P-2] Producing value : +5

  57. [P-2] Producing value : +6

  58. Queue is full, Producer[P-2] thread waiting for consumer to take something from queue.

  59. [C1] Consuming value : 2

  60. [C1] Consuming value : 3

  61. [C1] Consuming value : 4

  62. [C1] Consuming value : 5

  63. [C1] Consuming value : 6

  64. Queue is empty, Consumer[C1] thread is waiting for Producer

  65. Queue is empty, Consumer[C2] thread is waiting for Producer

  66. Queue is empty, Consumer[C3] thread is waiting for Producer

  67. [P-1] Producing value : +15

  68. [C3] Consuming value : 15

  69. Queue is empty, Consumer[C3] thread is waiting for Producer

  70. Queue is empty, Consumer[C2] thread is waiting for Producer

  71. Queue is empty, Consumer[C1] thread is waiting for Producer

  72. [P-2] Producing value : +7

  73. [P-2] Producing value : +8

  74. [P-2] Producing value : +9

2. 使用Lock和Condition的await() / signal()方法

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

 
  1. /**

  2. * 生产者消费者模式:使用Lock和Condition实现

  3. * {@link java.util.concurrent.locks.Lock}

  4. * {@link java.util.concurrent.locks.Condition}

  5. */

  6. public class ProducerConsumerByLock {

  7. private static final int CAPACITY = 5;

  8. private static final Lock lock = new ReentrantLock();

  9. private static final Condition fullCondition = lock.newCondition(); //队列满的条件

  10. private static final Condition emptyCondition = lock.newCondition(); //队列空的条件

  11.  
  12.  
  13. public static void main(String args[]){

  14. Queue<Integer> queue = new LinkedList<Integer>();

  15.  
  16. Thread producer1 = new Producer("P-1", queue, CAPACITY);

  17. Thread producer2 = new Producer("P-2", queue, CAPACITY);

  18. Thread consumer1 = new Consumer("C1", queue, CAPACITY);

  19. Thread consumer2 = new Consumer("C2", queue, CAPACITY);

  20. Thread consumer3 = new Consumer("C3", queue, CAPACITY);

  21.  
  22. producer1.start();

  23. producer2.start();

  24. consumer1.start();

  25. consumer2.start();

  26. consumer3.start();

  27. }

  28.  
  29. /**

  30. * 生产者

  31. */

  32. public static class Producer extends Thread{

  33. private Queue<Integer> queue;

  34. String name;

  35. int maxSize;

  36. int i = 0;

  37.  
  38. public Producer(String name, Queue<Integer> queue, int maxSize){

  39. super(name);

  40. this.name = name;

  41. this.queue = queue;

  42. this.maxSize = maxSize;

  43. }

  44.  
  45. @Override

  46. public void run(){

  47. while(true){

  48.  
  49. //获得锁

  50. lock.lock();

  51. while(queue.size() == maxSize){

  52. try {

  53. System.out .println("Queue is full, Producer[" + name + "] thread waiting for " + "consumer to take something from queue.");

  54. //条件不满足,生产阻塞

  55. fullCondition.await();

  56. } catch (InterruptedException ex) {

  57. ex.printStackTrace();

  58. }

  59. }

  60. System.out.println("[" + name + "] Producing value : +" + i);

  61. queue.offer(i++);

  62.  
  63. //唤醒其他所有生产者、消费者

  64. fullCondition.signalAll();

  65. emptyCondition.signalAll();

  66.  
  67. //释放锁

  68. lock.unlock();

  69.  
  70. try {

  71. Thread.sleep(new Random().nextInt(1000));

  72. } catch (InterruptedException e) {

  73. e.printStackTrace();

  74. }

  75. }

  76.  
  77. }

  78. }

  79.  
  80. /**

  81. * 消费者

  82. */

  83. public static class Consumer extends Thread{

  84. private Queue<Integer> queue;

  85. String name;

  86. int maxSize;

  87.  
  88. public Consumer(String name, Queue<Integer> queue, int maxSize){

  89. super(name);

  90. this.name = name;

  91. this.queue = queue;

  92. this.maxSize = maxSize;

  93. }

  94.  
  95. @Override

  96. public void run(){

  97. while(true){

  98. //获得锁

  99. lock.lock();

  100.  
  101. while(queue.isEmpty()){

  102. try {

  103. System.out.println("Queue is empty, Consumer[" + name + "] thread is waiting for Producer");

  104. //条件不满足,消费阻塞

  105. emptyCondition.await();

  106. } catch (Exception ex) {

  107. ex.printStackTrace();

  108. }

  109. }

  110. int x = queue.poll();

  111. System.out.println("[" + name + "] Consuming value : " + x);

  112.  
  113. //唤醒其他所有生产者、消费者

  114. fullCondition.signalAll();

  115. emptyCondition.signalAll();

  116.  
  117. //释放锁

  118. lock.unlock();

  119.  
  120. try {

  121. Thread.sleep(new Random().nextInt(1000));

  122. } catch (InterruptedException e) {

  123. e.printStackTrace();

  124. }

  125. }

  126. }

  127. }

  128. }

输入日志如下:

 
  1. [P-1] Producing value : +0

  2. [C1] Consuming value : 0

  3. Queue is empty, Consumer[C3] thread is waiting for Producer

  4. Queue is empty, Consumer[C2] thread is waiting for Producer

  5. [P-2] Producing value : +0

  6. [C3] Consuming value : 0

  7. Queue is empty, Consumer[C2] thread is waiting for Producer

  8. Queue is empty, Consumer[C1] thread is waiting for Producer

  9. [P-2] Producing value : +1

  10. [C2] Consuming value : 1

  11. Queue is empty, Consumer[C1] thread is waiting for Producer

  12. Queue is empty, Consumer[C3] thread is waiting for Producer

  13. [P-1] Producing value : +1

  14. [C1] Consuming value : 1

  15. Queue is empty, Consumer[C3] thread is waiting for Producer

  16. [P-1] Producing value : +2

  17. [C3] Consuming value : 2

  18. Queue is empty, Consumer[C2] thread is waiting for Producer

  19. [P-2] Producing value : +2

  20. [C2] Consuming value : 2

  21. Queue is empty, Consumer[C1] thread is waiting for Producer

  22. Queue is empty, Consumer[C2] thread is waiting for Producer

  23. [P-1] Producing value : +3

  24. [C1] Consuming value : 3

  25. Queue is empty, Consumer[C2] thread is waiting for Producer

  26. Queue is empty, Consumer[C1] thread is waiting for Producer

  27. Queue is empty, Consumer[C3] thread is waiting for Producer

  28. [P-2] Producing value : +3

  29. [C2] Consuming value : 3

  30. Queue is empty, Consumer[C1] thread is waiting for Producer

  31. Queue is empty, Consumer[C3] thread is waiting for Producer

  32. Queue is empty, Consumer[C2] thread is waiting for Producer

  33. [P-1] Producing value : +4

  34. [C1] Consuming value : 4

  35. Queue is empty, Consumer[C3] thread is waiting for Producer

  36. Queue is empty, Consumer[C2] thread is waiting for Producer

  37. Queue is empty, Consumer[C1] thread is waiting for Producer

  38. [P-2] Producing value : +4

  39. [C3] Consuming value : 4

  40. Queue is empty, Consumer[C2] thread is waiting for Producer

  41. Queue is empty, Consumer[C1] thread is waiting for Producer

  42. [P-2] Producing value : +5

  43. [C2] Consuming value : 5

  44. Queue is empty, Consumer[C1] thread is waiting for Producer

  45. Queue is empty, Consumer[C2] thread is waiting for Producer

  46. [P-1] Producing value : +5

  47. [C1] Consuming value : 5

  48. Queue is empty, Consumer[C2] thread is waiting for Producer

  49. Queue is empty, Consumer[C3] thread is waiting for Producer

  50. [P-2] Producing value : +6

  51. [C2] Consuming value : 6

  52. Queue is empty, Consumer[C3] thread is waiting for Producer

  53. [P-1] Producing value : +6

  54. [C3] Consuming value : 6

  55. Queue is empty, Consumer[C3] thread is waiting for Producer

  56. Queue is empty, Consumer[C1] thread is waiting for Producer

  57. [P-2] Producing value : +7

  58. [C3] Consuming value : 7

  59. Queue is empty, Consumer[C1] thread is waiting for Producer

  60. [P-1] Producing value : +7

  61. [C1] Consuming value : 7

  62. Queue is empty, Consumer[C2] thread is waiting for Producer

  63. [P-2] Producing value : +8

  64. [C2] Consuming value : 8

  65. [P-1] Producing value : +8

  66. [C1] Consuming value : 8

  67. [P-2] Producing value : +9

  68. [C3] Consuming value : 9

  69. [P-2] Producing value : +10

  70. [C2] Consuming value : 10

  71. [P-1] Producing value : +9

  72. [P-1] Producing value : +10

  73. [C1] Consuming value : 9

  74. [P-2] Producing value : +11

  75. [C3] Consuming value : 10

  76. [C2] Consuming value : 11

  77. [P-2] Producing value : +12

  78. [C1] Consuming value : 12

  79. [P-1] Producing value : +11

  80. [C3] Consuming value : 11

  81. [P-2] Producing value : +13

  82. [C2] Consuming value : 13

  83. Queue is empty, Consumer[C2] thread is waiting for Producer

  84. Queue is empty, Consumer[C3] thread is waiting for Producer

  85. [P-1] Producing value : +12

  86. [C2] Consuming value : 12

  87. Queue is empty, Consumer[C3] thread is waiting for Producer

  88. [P-1] Producing value : +13

  89. [C3] Consuming value : 13

  90. Queue is empty, Consumer[C1] thread is waiting for Producer

  91. Queue is empty, Consumer[C3] thread is waiting for Producer

  92. [P-2] Producing value : +14

  93. [C1] Consuming value : 14

  94. Queue is empty, Consumer[C3] thread is waiting for Producer

  95. Queue is empty, Consumer[C1] thread is waiting for Producer

  96. [P-1] Producing value : +14

  97. [C3] Consuming value : 14

  98. Queue is empty, Consumer[C1] thread is waiting for Producer

  99. [P-1] Producing value : +15

  100. [C1] Consuming value : 15

  101. [P-2] Producing value : +15

  102. [P-1] Producing value : +16

  103. [C3] Consuming value : 15

  104. [P-2] Producing value : +16

3. 使用BlockingQueue阻塞队列方法

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

  • java.util.concurrent.ArrayBlockingQueue
  • java.util.concurrent.LinkedBlockingQueue
  • java.util.concurrent.SynchronousQueue
  • java.util.concurrent.PriorityBlockingQueue

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

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

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

我们可以跟进源码看一下LinkedBlockingQueue类的put()方法实现:

 
  1. /** Main lock guarding all access */

  2. final ReentrantLock lock = new ReentrantLock();

  3.  
  4. /** Condition for waiting takes */

  5. private final Condition notEmpty = lock.newCondition();

  6.  
  7. /** Condition for waiting puts */

  8. private final Condition notFull = lock.newCondition();

  9.  
  10.  
  11.  
  12. public void put(E e) throws InterruptedException {

  13. putLast(e);

  14. }

  15.  
  16. public void putLast(E e) throws InterruptedException {

  17. if (e == null) throw new NullPointerException();

  18. Node<E> node = new Node<E>(e);

  19. final ReentrantLock lock = this.lock;

  20. lock.lock();

  21. try {

  22. while (!linkLast(node))

  23. notFull.await();

  24. } finally {

  25. lock.unlock();

  26. }

  27. }

看到这里证实了它的实现方式采用的是我们第2种await()signal()方法。下面我们就使用它实现吧。

 
  1. /**

  2. * 生产者消费者模式:使用{@link java.util.concurrent.BlockingQueue}实现

  3. */

  4. public class ProducerConsumerByBQ{

  5. private static final int CAPACITY = 5;

  6.  
  7. public static void main(String args[]){

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

  9.  
  10. Thread producer1 = new Producer("P-1", blockingQueue, CAPACITY);

  11. Thread producer2 = new Producer("P-2", blockingQueue, CAPACITY);

  12. Thread consumer1 = new Consumer("C1", blockingQueue, CAPACITY);

  13. Thread consumer2 = new Consumer("C2", blockingQueue, CAPACITY);

  14. Thread consumer3 = new Consumer("C3", blockingQueue, CAPACITY);

  15.  
  16. producer1.start();

  17. producer2.start();

  18. consumer1.start();

  19. consumer2.start();

  20. consumer3.start();

  21. }

  22.  
  23. /**

  24. * 生产者

  25. */

  26. public static class Producer extends Thread{

  27. private LinkedBlockingDeque<Integer> blockingQueue;

  28. String name;

  29. int maxSize;

  30. int i = 0;

  31.  
  32. public Producer(String name, LinkedBlockingDeque<Integer> queue, int maxSize){

  33. super(name);

  34. this.name = name;

  35. this.blockingQueue = queue;

  36. this.maxSize = maxSize;

  37. }

  38.  
  39. @Override

  40. public void run(){

  41. while(true){

  42. try {

  43. blockingQueue.put(i);

  44. System.out.println("[" + name + "] Producing value : +" + i);

  45. i++;

  46.  
  47. //暂停最多1秒

  48. Thread.sleep(new Random().nextInt(1000));

  49. } catch (InterruptedException e) {

  50. e.printStackTrace();

  51. }

  52. }

  53.  
  54. }

  55. }

  56.  
  57. /**

  58. * 消费者

  59. */

  60. public static class Consumer extends Thread{

  61. private LinkedBlockingDeque<Integer> blockingQueue;

  62. String name;

  63. int maxSize;

  64.  
  65. public Consumer(String name, LinkedBlockingDeque<Integer> queue, int maxSize){

  66. super(name);

  67. this.name = name;

  68. this.blockingQueue = queue;

  69. this.maxSize = maxSize;

  70. }

  71.  
  72. @Override

  73. public void run(){

  74. while(true){

  75. try {

  76. int x = blockingQueue.take();

  77. System.out.println("[" + name + "] Consuming : " + x);

  78.  
  79. //暂停最多1秒

  80. Thread.sleep(new Random().nextInt(1000));

  81. } catch (InterruptedException e) {

  82. e.printStackTrace();

  83. }

  84. }

  85. }

  86. }

  87. }

输出日志如下:

 
  1. [P-2] Producing value : +0

  2. [P-1] Producing value : +0

  3. [C1] Consuming : 0

  4. [C3] Consuming : 0

  5. [P-2] Producing value : +1

  6. [C2] Consuming : 1

  7. [P-2] Producing value : +2

  8. [C1] Consuming : 2

  9. [P-1] Producing value : +1

  10. [C2] Consuming : 1

  11. [P-1] Producing value : +2

  12. [C3] Consuming : 2

  13. [P-1] Producing value : +3

  14. [C2] Consuming : 3

  15. [P-2] Producing value : +3

  16. [C1] Consuming : 3

  17. [P-1] Producing value : +4

  18. [C2] Consuming : 4

  19. [P-2] Producing value : +4

  20. [C3] Consuming : 4

  21. [P-2] Producing value : +5

  22. [C1] Consuming : 5

  23. [P-1] Producing value : +5

  24. [C2] Consuming : 5

  25. [P-1] Producing value : +6

  26. [C1] Consuming : 6

  27. [P-2] Producing value : +6

  28. [C2] Consuming : 6

  29. [P-2] Producing value : +7

  30. [C2] Consuming : 7

  31. [P-1] Producing value : +7

  32. [C1] Consuming : 7

  33. [P-2] Producing value : +8

  34. [C3] Consuming : 8

  35. [P-2] Producing value : +9

  36. [C2] Consuming : 9

  37. [P-1] Producing value : +8

  38. [C2] Consuming : 8

  39. [P-2] Producing value : +10

  40. [C1] Consuming : 10

  41. [P-1] Producing value : +9

  42. [C3] Consuming : 9

  43. [P-1] Producing value : +10

  44. [C2] Consuming : 10

  45. [P-2] Producing value : +11

  46. [C1] Consuming : 11

  47. [C3] Consuming : 12

  48. [P-2] Producing value : +12

  49. [P-2] Producing value : +13

  50. [C2] Consuming : 13

  51. [P-1] Producing value : +11

  52. [C3] Consuming : 11

  53. [P-1] Producing value : +12

  54. [C3] Consuming : 12

  55. [P-2] Producing value : +14

  56. [C1] Consuming : 14

  57. [P-1] Producing value : +13

  58. [C2] Consuming : 13

  59. [P-2] Producing value : +15

  60. [C3] Consuming : 15

  61. [P-2] Producing value : +16

  62. [C1] Consuming : 16

  63. [P-1] Producing value : +14

  64. [C3] Consuming : 14

  65. [P-2] Producing value : +17

  66. [C2] Consuming : 17

参考资料

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值