生产者消费者问题:
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。
生产者消费者模式的优点:
- 解耦
- 支持并发
- 支持忙闲不均
解决方法可分为两类:
- 用信号量和锁机制实现生产者和消费者之间的同步
- wait() / notify()方法
- await() / signal()方法
- BlockingQueue阻塞队列方法
- Semaphore方法
- 在生产者和消费者之间建立一个管道。(一般不使用,缓冲区不易控制、数据不易封装和传输)
- PipedInputStream / PipedOutputStream
wait() / notify()方法
publicclassTest{
privatestaticIntegercount=0;
privatefinalIntegerFULL=5;
privatestaticStringlock="lock";
publicstaticvoidmain(String[]args){
Testt=newTest();
newThread(t.newProducer()).start();
newThread(t.newConsumer()).start();
newThread(t.newProducer()).start();
newThread(t.newConsumer()).start();
}
classProducerimplementsRunnable{
@Override
publicvoidrun(){
for(inti=0;i<5;i++){
try{
Thread.sleep(1000);
}catch(InterruptedExceptione1){
e1.printStackTrace();
}
synchronized(lock){
while(count==FULL){
try{
lock.wait();
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
count++;
System.out.println("生产者"+Thread.currentThread().getName()
+"已生产完成,商品数量:"+count);
lock.notifyAll();
}
}
}
}
classConsumerimplementsRunnable{
@Override
publicvoidrun(){
for(inti=0;i<5;i++){
try{
Thread.sleep(1000);
}catch(InterruptedExceptione1){
e1.printStackTrace();
}
synchronized(lock){
while(count==0){
try{
lock.wait();
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
count--;
System.out.println("消费者"+Thread.currentThread().getName()
+"已消费,剩余商品数量:"+count);
lock.notifyAll();
}
}
}
}
}
运行结果:
生产者Thread-0已生产完成,商品数量:1
生产者Thread-2已生产完成,商品数量:2
消费者Thread-1已消费,剩余商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
生产者Thread-2已生产完成,商品数量:2
消费者Thread-1已消费,剩余商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
await() / signal()方法
await()/signal()是对wait()/notify()的改进,功能更加强大,更适用于高级用户,synchronized是托管给JVM执行的,而lock是Java写的控制锁的代码。
下面是使用ReentrantLock来实现生产者消费者问题:
publicclassTest{
privatestaticIntegercount=0;//缓冲区
privatefinalIntegerFULL=5;
finalLocklock=newReentrantLock();//获得可重入锁
finalConditionput=lock.newCondition();
finalConditionget=lock.newCondition();
publicstaticvoidmain(String[]args){
Testt=newTest();
newThread(t.newProducer()).start();
newThread(t.newConsumer()).start();
newThread(t.newConsumer()).start();
newThread(t.newProducer()).start();
}
//生产者
classProducerimplementsRunnable{
@Override
publicvoidrun(){
for(inti=0;i<5;i++){
try{
Thread.sleep(1000);
}catch(InterruptedExceptione1){
e1.printStackTrace();
}
//加锁
lock.lock();
try{
while(count==FULL){
try{
put.await();
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
count++;
System.out.println("生产者"+Thread.currentThread().getName()
+"已生产完成,商品数量:"+count);
//通知消费者,现在可以消费
get.signal();
}finally{
lock.unlock();
}
}
}
}
classConsumerimplementsRunnable{
@Override
publicvoidrun(){
for(inti=0;i<5;i++){
try{
Thread.sleep(1000);
}catch(InterruptedExceptione1){
e1.printStackTrace();
}
lock.lock();
try{
while(count==0){
try{
get.await();
}catch(Exceptione){
e.printStackTrace();
}
}
count--;
System.out.println("消费者"+Thread.currentThread().getName()
+"已消费,剩余商品数量:"+count);
put.signal();
}finally{
lock.unlock();
}
}
}
}
}
运行结果:
生产者Thread-3已生产完成,商品数量:1
生产者Thread-0已生产完成,商品数量:2
消费者Thread-1已消费,剩余商品数量:1
消费者Thread-2已消费,剩余商品数量:0
生产者Thread-3已生产完成,商品数量:1
生产者Thread-0已生产完成,商品数量:2
消费者Thread-1已消费,剩余商品数量:1
消费者Thread-2已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
生产者Thread-3已生产完成,商品数量:2
消费者Thread-1已消费,剩余商品数量:1
消费者Thread-2已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
生产者Thread-3已生产完成,商品数量:2
消费者Thread-2已消费,剩余商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-3已生产完成,商品数量:1
生产者Thread-0已生产完成,商品数量:2
消费者Thread-2已消费,剩余商品数量:1
消费者Thread-1已消费,剩余商品数量:0
BlockingQueue阻塞队列方法
BlockingQueue实现主要用于生产者-使用者队列,但它另外还支持Collection接口。是线程安全的,所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们的目的。
用于阻塞的两个方法:
- put()方法:将指定元素插入此队列中,将等待可用的空间(如果有必要)。
- take()方法:获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。
public class Test {
private static Integer count = 0;
final BlockingQueue<Integer> bq = new ArrayBlockingQueue<Integer>(5);// 容量为5的阻塞队列
public static void main(String[] args) {
Test t = new Test();
new Thread(t.new Producer()).start();
new Thread(t.new Consumer()).start();
new Thread(t.new Consumer()).start();
new Thread(t.new Producer()).start();
}
class Producer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
try {
bq.put(1);
count++;
System.out.println("生产者" + Thread.currentThread().getName()
+ "已生产完成,商品数量:" + count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
bq.take();
count--;
System.out.println("消费者" + Thread.currentThread().getName()
+ "已消费,剩余商品数量:" + count);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
生产者Thread-0已生产完成,商品数量:0
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-3已生产完成,商品数量:0
消费者Thread-2已消费,剩余商品数量:0
生产者Thread-3已生产完成,商品数量:1
消费者Thread-2已消费,剩余商品数量:1
生产者Thread-0已生产完成,商品数量:2
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:2
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-3已生产完成,商品数量:2
消费者Thread-2已消费,剩余商品数量:1
生产者Thread-0已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:1
生产者Thread-3已生产完成,商品数量:2
消费者Thread-2已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:0
消费者Thread-2已消费,剩余商品数量:0
生产者Thread-3已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
Semaphore方法实现同步
信号量(Semaphore)维护了一个许可集。在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动。Semaphore通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。
注意,调用acquire()时无法保持同步锁,因为这会阻止将项返回到池中。信号量封装所需的同步,以限制对池的访问,这同维持该池本身一致性所需的同步是分开的。
public class Test {
int count = 0;
final Semaphore put = new Semaphore(5);// 初始令牌个数
final Semaphore get = new Semaphore(0);
final Semaphore mutex = new Semaphore(1);
public static void main(String[] args) {
Test t = new Test();
new Thread(t.new Producer()).start();
new Thread(t.new Consumer()).start();
new Thread(t.new Consumer()).start();
new Thread(t.new Producer()).start();
}
class Producer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
try {
put.acquire();// 注意顺序
mutex.acquire();
count++;
System.out.println("生产者" + Thread.currentThread().getName()
+ "已生产完成,商品数量:" + count);
} catch (Exception e) {
e.printStackTrace();
} finally {
mutex.release();
get.release();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
get.acquire();// 注意顺序
mutex.acquire();
count--;
System.out.println("消费者" + Thread.currentThread().getName()
+ "已消费,剩余商品数量:" + count);
} catch (Exception e) {
e.printStackTrace();
} finally {
mutex.release();
put.release();
}
}
}
}
}
运行结果:
生产者Thread-0已生产完成,商品数量:1
消费者Thread-2已消费,剩余商品数量:0
生产者Thread-3已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
生产者Thread-3已生产完成,商品数量:2
消费者Thread-2已消费,剩余商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
生产者Thread-3已生产完成,商品数量:2
消费者Thread-2已消费,剩余商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
生产者Thread-3已生产完成,商品数量:2
消费者Thread-2已消费,剩余商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
生产者Thread-3已生产完成,商品数量:2
消费者Thread-2已消费,剩余商品数量:1
消费者Thread-1已消费,剩余商品数量:0
PipedInputStream / PipedOutputStream
这个类位于java.io包中,是解决同步问题的最简单的办法,一个线程将数据写入管道,另一个线程从管道读取数据,这样便构成了一种生产者/消费者的缓冲区编程模式。PipedInputStream/PipedOutputStream只能用于多线程模式,用于单线程下可能会引发死锁。
public class Test {
final PipedInputStream pis = new PipedInputStream();
final PipedOutputStream pos = new PipedOutputStream();
public static void main(String[] args) {
Test t = new Test();
new Thread(t.new Producer()).start();
new Thread(t.new Consumer()).start();
}
class Producer implements Runnable {
@Override
public void run() {
try {
pis.connect(pos);
} catch (IOException e) {
e.printStackTrace();
}
try {
while (true) { // 不断的产生数据
int n = (int) (Math.random() * 255);
System.out.println("生产者" + Thread.currentThread().getName()
+ "已生产完成,商品数量:" + n);
pos.write(n);
pos.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
pis.close();
pos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
int n;
try {
while (true) {
n = pis.read();
System.out.println("消费者" + Thread.currentThread().getName()
+ "已消费,剩余商品数量:" + n);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
pis.close();
pos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
生产者Thread-0已生产完成,商品数量:6
生产者Thread-0已生产完成,商品数量:158
生产者Thread-0已生产完成,商品数量:79
生产者Thread-0已生产完成,商品数量:119
生产者Thread-0已生产完成,商品数量:93
生产者Thread-0已生产完成,商品数量:213
生产者Thread-0已生产完成,商品数量:151
生产者Thread-0已生产完成,商品数量:101
生产者Thread-0已生产完成,商品数量:125
生产者Thread-0已生产完成,商品数量:109
生产者Thread-0已生产完成,商品数量:67
生产者Thread-0已生产完成,商品数量:109
生产者Thread-0已生产完成,商品数量:132
生产者Thread-0已生产完成,商品数量:139