经典线程同步问题(生产者&消费者)--Java实现
生产者-消费者(producer-consumer)问题是一个著名的线程同步问题。它描述的是:有一群生产者线程在生产产品,并将这些产品提供给消费者线程去消费。
为使生产者与消费者之间能够并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者将它所生产的产品放入一个缓冲区中;消费者可以从一个缓冲区中取走产品产生消费。
尽管所有的生产者线程和消费者线程都是以异步方式运行的,但他们之间必须保持同步,即不允许消费者到一个空缓冲区去消费,也不允许生产者向一个已经被占用的缓冲区投放产品。
我把这个问题复杂化,设立m个缓冲池,每个缓冲池都有各自固定的容量,每个生产者或消费者在进行生产消费活动之前,先选择一个缓冲池。由此会引发一 个线程死锁的问题:所有的生产者都在满的缓冲池等待,直到某个消费者取走一个产品,释放出一块缓冲区;同时所有的消费者都在空的缓冲池等待,直到某个生产 者放进一个产品。
解决方法:记录每一个线程的等待状态,如果当前线程会产生等待,则检测是否会产生死锁(所有线程都在等待),如果会产生死锁,拒绝此次生产消费活动(换一个缓冲池)
Java Code:
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
private static final int PRODUCTION_LINE_COUNT = 20 ; //生产线(缓冲池)条数 private static final int PRODUCTION_LINE_SIZE = 100 ; //生产线最大容量 private static final int PRODUCER_COUNT = 50 ; //生产者个数 private static final int CONSUMER_COUNT = 50 ; //消费者个数 public static void main(String[] args) { ProductionLine[] productionLine = new ProductionLine[PRODUCTION_LINE_COUNT]; Producer[] producer = new Producer[PRODUCER_COUNT]; Consumer[] consumer = new Consumer[CONSUMER_COUNT]; SyncLock.initLock(PRODUCER_COUNT, CONSUMER_COUNT); //初始化每个线程的等待状态 for ( int i = 0 ; i < PRODUCTION_LINE_COUNT; i++) { //初始化每条生产线,随机容量 productionLine[i] = new ProductionLine(( int ) (Math.random() * PRODUCTION_LINE_SIZE) + 1 , i); } for ( int i = 0 ; i < PRODUCER_COUNT; i++) { //初始化生产者线程 producer[i] = new Producer(i, productionLine); new Thread(producer[i]).start(); } for ( int i = 0 ; i < CONSUMER_COUNT; i++) { //初始化消费者线程 consumer[i] = new Consumer(i, productionLine); new Thread(consumer[i]).start(); } } } class Product { //产品(缓冲区)类 int productID; //制造者编号 int productionLineID; //生产线编号 int producerID; //产品编号(产品在该生产线上的位置) Product( int producerID) { this .producerID = producerID; } } class ProductionLine { //生产线(缓冲池)类 Product[] product; //缓冲池采用循环队列模式 int size; //缓冲池大小 int count; //缓冲池当前产品计数 int productionLineID; //生产线编号 int produceID; //队头指针,下一个被放入的产品的编号 int consumeID; //队尾指针,下一个被取走的产品的编号 ProductionLine( int size, int productionLineID) { //初始化生产线(缓冲池) this .size = size; this .productionLineID = productionLineID; product = new Product[size]; for ( int i = 0 ; i < size / 2 ; i++) { //为防止刚开始产销不平衡,预先放置一半产品 putProduct( new Product(- 1 )); //产品生产者编号-1,系统制造 } } boolean isFull() { //判断缓冲池是否满 return count == size; } boolean isEmpty() { //判断缓冲池是否空 return 0 == count; } void putProduct(Product product) { //放入一个产品,并将队头指针前移 this .product[produceID] = product; product.productID = produceID; //给产品贴上标签,产品编号,生产线编号 product.productionLineID = productionLineID; produceID = ++produceID % size; //下一个产品放置位置 count++; } Product getProduct() { //取出一个产品,并将队尾指针前移 Product product = this .product[consumeID]; this .product[consumeID] = null ; consumeID = ++consumeID % size; //下一个产品取出位置 count--; return product; } } class Producer implements Runnable { //生产者线程 int producerID; //自己的生产者编号 ProductionLine[] productionLine; //共享的生产线 ProductionLine currentProductionLine; Producer( int producerID, ProductionLine[] productionLine) { this .producerID = producerID; this .productionLine = productionLine; } private void produce() { //生产活动 Product product; int productionLineID; while ( true ) { do { //选择一条生产线,若会产生死锁,则重选一条生产线 productionLineID = ( int ) (Math.random() * productionLine.length); currentProductionLine = productionLine[productionLineID]; } while ((SyncLock.waitOnFull[producerID] = currentProductionLine.isFull()) && SyncLock.deadLock()); //synchronized(SyncLock.printSyncLock) { // System.out.print("Producer " + producerID +" wants to "); // System.out.println("produce at ProductionLine " + productionLineID); //} synchronized (currentProductionLine) { //同步对象:当前生产线,不能同时有两个及以上线程操作同一生产线 while (currentProductionLine.isFull()) { //缓冲池满,无法生产,等待 try { currentProductionLine.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } product = new Product(producerID); //生产新的产品 currentProductionLine.putProduct(product); //加入缓冲池 synchronized (SyncLock.printSyncLock) { //打印生产线、生产者、产品ID及缓冲池是否变满 System.out.print( "ProductionLine: " + productionLineID); System.out.print( "\tProducer: " + producerID); System.out.print( "\tProduct: " + product.productID); System.out.println(currentProductionLine.isFull() ? "\t(" + productionLineID + ")Full!" : "" ); } //释放当前生产线同步锁 currentProductionLine.notifyAll(); //生产活动结束,唤醒当前生产线上等待的其他生产者/消费者线程 } try { Thread.sleep(( int ) (Math.random() * 1000 )); } catch (InterruptedException e) { e.printStackTrace(); } } } public void run() { produce(); } } class Consumer implements Runnable { int consumerID; //自己的消费者编号 ProductionLine[] productionLine; //共享的生产线 ProductionLine currentProductionLine; Consumer( int consumerID, ProductionLine[] productionLine) { this .consumerID = consumerID; this .productionLine = productionLine; } private void consume() { //消费活动 Product product; int productionLineID; while ( true ) { do { //选择一条生产线,若会产生死锁,则重选一条生产线 productionLineID = ( int ) (Math.random() * productionLine.length); currentProductionLine = productionLine[productionLineID]; } while ((SyncLock.waitOnEmpty[consumerID] = currentProductionLine.isEmpty()) && SyncLock.deadLock()); //synchronized(SyncLock.printSyncLock) { // System.out.print("Consumer " + consumerID +" wants to "); // System.out.println("consume at ProductionLine " + productionLineID); //} synchronized (currentProductionLine) { //同步对象:当前生产线,不能同时有两个及以上线程操作同一生产线 while (currentProductionLine.isEmpty()) { //缓冲池空,无法消费,等待 try { currentProductionLine.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } product = currentProductionLine.getProduct(); //消费产品 synchronized (SyncLock.printSyncLock) { //打印生产线、消费者、产品及其制造者ID,以及生产线是否变空 System.out.print( "ProductionLine: " + productionLineID); System.out.print( "\tConsumer: " + consumerID); System.out.print( "\tProduct: " + product.productID); System.out.print( "(" + product.producerID + ")" ); System.out.println(currentProductionLine.isEmpty() ? "\t(" + productionLineID + ")Empty!" : "" ); } currentProductionLine.notifyAll(); //生产活动结束,唤醒当前生产线上等待的其他生产者/消费者线程 } try { Thread.sleep(( int ) (Math.random() * 1000 )); } catch (InterruptedException e) { e.printStackTrace(); } } } public void run() { consume(); } } class SyncLock { //提供死锁检测的静态方法,及打印同步 static final Object printSyncLock = new Object(); //用于同步输出,防止不同线程输出交叉 static boolean [] waitOnEmpty; //指示某一消费者是否在空缓冲池等待 static boolean [] waitOnFull; //指示某一生产者是否在满缓冲池等待 static void initLock( int ProducerCount, int ConsumerCount) { //初始化等待指示器 waitOnEmpty = new boolean [ConsumerCount]; waitOnFull = new boolean [ProducerCount]; } static boolean deadLock() { //判断是否产生死锁 for ( boolean b : waitOnEmpty) { if (!b) return false ; } for ( boolean b : waitOnFull) { if (!b) return false ; } return true ; //若所有线程都在等待状态,则产生死锁 } } |
经典线程同步问题(生产者&消费者)--Java实现的更多相关文章
- Java经典线程同步问题------生产者与消费者
先上代码 class Test { public static void main(String []args) { Queue q=new Queue(); Producer p=new Produ ...
- 经典线程同步 信号量Semaphore
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...
- 秒杀多线程第八篇 经典线程同步 信号量Semaphore
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <且不超过最大资源数量. 第三个參数能够用来传出先前的资源计数,设为NULL表示不须要传出. 注意:当 ...
- Linux线程编程之生产者消费者问题
前言 本文基于顺序循环队列,给出Linux生产者/消费者问题的多线程示例,并讨论编程时需要注意的事项.文中涉及的代码运行环境如下: 本文假定读者已具备线程同步的基础知识. 一 顺序表循环队列 1.1 ...
- 转---秒杀多线程第八篇 经典线程同步 信号量Semaphore
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...
- Linux线程编程之生产者消费者问题【转】
转自:http://www.cnblogs.com/clover-toeic/p/4029269.html 前言 本文基于顺序循环队列,给出Linux生产者/消费者问题的多线程示例,并讨论编程时需要注 ...
- 经典线程同步 互斥量Mutex
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...
- 经典线程同步 事件Event
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇 一个经典的多线程同步问题> <秒杀多线程第五篇 经典线程同步关键段CS> 上一篇中使用关键段来解决经典的多线程同步互斥问题 ...
- 经典线程同步 关键段CS
上一篇<秒杀多线程第四篇 一个经典的多线程同步问题>提出了一个经典的多线程同步互斥问题,本篇将用关键段CRITICAL_SECTION来尝试解决这个问题. 本文首先介绍下如何使用关键段,然 ...
随机推荐
- C语言模拟ATM机界面
虽然是满屏的printf.printf.printf.printf......尴尬 但是一个小项目做下来还是能学习到很多的,有很多小的问题,不是亲自来敲一遍代码,是不会发现的.他的框架,每一个小函数功 ...
- Redis快速起步及Redis常用命令大全
本系列教程内容提要 Java工程师之Redis实战系列教程教程是一个学习教程,是关于Java工程师的Redis知识的实战系列教程,本系列教程均以解决特定问题为目标,使用Redis快速解决在实际生产中的 ...
- SQL SERVER大话存储结构(5)_SQL SERVER 事务日志解析
本系列上一篇博文链接:SQL SERVER大话存储结构(4)_复合索引与包含索引 1 基本介绍 每个数据库都具有事务日志,用于记录所有事物以及每个事物对数据库所作的操作. 日志的记录 ...
- centos7修改网卡名、密码重置
修改网卡名称 编辑 /etc/sysconfig/grub 倒数第二行quiet 后加入 net.ifnames=0 biosdevname=0 执行 grub2-mkconfig -o /bo ...
- android中Logcat的TAG过滤
如果代码中有这样的log: Log.e("Foo", "error in foo"); Log.d("Foo", "debug i ...
- ArcGIS for Server的安装及站点中的集群配置 分类: ArcGIS for server 2015-07-18 14:14 16人阅读 评论(0) 收藏
坚信并为之坚持是一切希望的原因. (不足之处,欢迎批评指正!) --------------------环境:Windows server2008R2虚拟机两台----------------- ...
- mysql-blog
https://www.cnblogs.com/zhanht/p/5450559.html
- Underscore.js (1.7.0)-函数预览
集合(Collections)(25) - each - map - reduce - reduceRight - find - filter - where - findWhere - reject ...
- java反射获得泛型参数getGenericSuperclass():获取到父类泛型的类型
public class Person<T> { } import java.lang.reflect.ParameterizedType; import java.lang.reflec ...
- 模块讲解----反射 (基于web路由的反射)
一.反射的实际案例: def main(): menu = ''' 1.账户信息 2.还款 3.取款 4.转账 5.账单 ''' menu_dic = { ':account_info, ':repa ...