经典的多线程同步问题!详细解析多线程中的生产者和消费者问题

这是我参与11月更文挑战的第24天,活动详情查看:2021最后一次更文挑战

生产者消费者模型

  • 生产者消费者模型包括生产者,消费者,仓库,产品:
    • 生产者在仓库未满时才能生产,仓库满时停止生产
    • 消费者在仓库有产品时才能消费,仓库空时等待
    • 当消费者发现仓库为空,没有产品可以消费时,通知生产者生产
    • 生产者在生产出可以消费的产品时,通知等待的消费者消费

生产者消费者实现

  • 生产者消费者示例
  • 线程池中可以实现生产者消费者模型
  • 这里通过简单的wait()notify() 方式实现生产者消费者模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
java复制代码class Depot {
/**
* 仓库容量
*/
private int capacity;
/**
* 仓库的实际容量
*/
private int size = 0;

public Depot(int capacity) {
this.capacity = capacity;
this.size = 0;
}

/**
* 生产方法
*
* @param val 需要生产的数量
*/
public synchronized void produce(int val) {
try {
// want表示想要生产的数量.因为可能想要生产的数量太多,需要进行多次生产
int want = val;
while (want > 0) {
// 如果库存已满时,等待消费者消费产品
while (size >= capacity) {
wait();
}
/*
* 获取实际生产的数量,即新增的产品数量
* - 如果库存和想要生产的数量之和大于仓库容量时,则实际生产数量等于总的容量减去当前容量
* - 如果库存和想要生产的数量之和小于等于仓库容量,则实际生产数量等于想要生产的数量
*/
int increment = (size + want) > capacity ? (capacity - size) : want;
size += increment;
want -= increment;
System.out.printf("%sproduce(%3d) --> want=%3d, increment=%3d, size=%3d\n", Thread.currentThread().getName(), val, want, increment, size);
// 生产完成通知消费者消费
notifyAll();
}
} catch (InterruptedException e) {
// 不需要进行处理
}
}

/**
* 消费方法
*
* @param val 消费的数量
*/
public synchronized void consume(int val) {
try {
// want表示客户消费的数量.因为想要消费的数量太多,需要进行多次消费
int want = val;
while (want > 0) {
// 如果库存为空时,等待生产者生产产品
while (size <= 0) {
wait();
}
/*
* 获取实际消费的数量,即减少的数量
* - 如果库存的数量小于想要消费的数量,则实际消费数量等于库存量
* - 如果库存的数量等于或者大于想要消费的数量,则实际消费数量等于想要消费的数量
*/
int decrease = (size < want) ? size : want;
size -= decrease;
want -= decrease;
System.out.printf("%s consume(%3d) <-- want=%3d, decrease=%3d, size=%3d\n");
// 消费完成通知生产者生产
notifyAll();
}
} catch (InterruptedException e) {
// 不需要进行处理
}
}
}

class Producer {
private Depot depot;

public Producer(Depot depot) {
this.depot = depot;
}

/**
* 新建一个生产者线程,用于生产产品
*
* @param val 需要生产的数量
*/
public void produce(final int val) {
new Thread () {
public void run() {
depot.produce(val);
}
}.start();
}
}

class Consumer {
private Depot depot;

public Consumer(Depot depot) {
this.depot = depot;
}

/**
* 新建一个消费者,用于消费产品
*
* @param val 需要消费的数量
*/
public void consume(final int val) {
new Thread() {
public void run() {
depot.consume(val);
}
}.start();
}
}

class ProducerConsumerTest {
public static void main(String[] args) {
Depot depot = new Depot(100);
Producer producer = new Producer(depot);
Consumer consumer = new Consumer(depot);

producer.produce(60);
producer.produce(120);
consumer.consume(90);
consumer.consume(150);
producer.produce(110);
}
}
1
2
3
4
5
6
7
8
9
console复制代码Thread-0 produce( 60) --> want=  0, increment= 60, size= 60
Thread-4 produce(110) --> want= 70, increment= 40, size=100
Thread-2 consume( 90) <-- want= 0, decrease= 90, size= 10
Thread-3 consume(150) <-- want=140, decrease= 10, size= 0
Thread-1 produce(120) --> want= 20, increment=100, size=100
Thread-3 consume(150) <-- want= 40, decrease=100, size= 0
Thread-4 produce(110) --> want= 0, increment= 70, size= 70
Thread-3 consume(150) <-- want= 0, decrease= 40, size= 30
Thread-1 produce(120) --> want= 0, increment= 20, size= 50
  • Producer是生产者类,与仓库depot关联. 当调用生产者的produce() 方法时,会新建一个线程并向仓库depot中生产产品
  • Consumer是消费者类,与仓库depot关联. 当调用消费者的consume() 方法时,会新建一个线程并消费仓库depot中的商品
  • Depot是仓库类,仓库类Depot中记录仓库的容量capacity, 以及仓库中当前产品的数目size :
    • 仓库类Depot的生产方法produce() 和消费方法consume() 方法都是synchronized方法,进入synchronized方法体,意味着这个线程获取了该仓库对象的同步锁
    • 意味着,同一时间生产者和消费者线程只能有一个能运行. 通过同步锁,实现了对仓库的互斥访问
    • produce()生产方法:
      • 当仓库满时,生产者线程等待,需要等待消费者消费产品后,生产者线程才能生产
      • 生产者线程生产完产品之后,会通过notifyAll() 方法唤醒同步锁上的所有线程,包括消费者线程,这样就可以通知消费者进行消费
    • consume()消费方法:
      • 当仓库为空时,消费者线程等待,需要等待生产者生产产品之后,消费者线程才能消费
      • 消费者线程消费完产品之后,会通过notifyAll() 方法唤醒同步锁上的所有线程,包括生产者线程,这样就可以通知生产者进行生产

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%