1生产者消费者问题
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者消费者模式的特点。:
*解耦
*支持并发
*支持忙闲不均
解决方法可分为两类:
(1)使用信号量和锁机制实现生产者消费者之间的同步;
* wait()/notify()方法
* await()/signal方法
* BlockingQueue阻塞队列方法
* Semaphore方法
* PipedInputStream/PipedOutputStream
(2) wait()/notify方法实现
wait()/notify()方法是object里面的两个方法,所有Object的子类都可以使用这两个方法。
测试代码:
public class test {
private static Integer count=0;
private final Integer FULL=5;
private static String lock="lock";
public static void main(String[] args){
test t=new test();
//System.out.println("生产者"+Thread.currentThread().getName()+"已生产完成,商品数量:"+count);
new Thread(t.new Producer()).start();
new Thread(t.new Consumer()).start();
new Thread(t.new Producer()).start();
new Thread(t.new Consumer()).start();
}
public class Producer implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(lock){
while(count==FULL){
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
count++;
System.out.println("生产者"+Thread.currentThread().getName()+"已生产完成,商品数量:"+count);
lock.notifyAll();
}
}
}
}
public class Consumer implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(lock){
while(count==0){
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
count--;
System.out.println("消费者"+Thread.currentThread().getName()+"已消费,剩余商品数量:"+count);
lock.notifyAll();
}
}
}
}
}
对象的监视器对锁对象的锁定(也就是代码中的lock对象)注意调用的是锁对象的wait()/nofity()方法。
运行结果:
生产者Thread-0已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
await()/signal()是对wait()/notify()的改进,功能更加强大,更实用于高级用户,synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。更多关于ReentrantLock的介绍,请前往以下地址查看《synchronized、lock、reentrantLock 区别》下面是使用ReentrantLock来实现生产者消费者问题。
public class TestReentrantLock {
private static Integer count =0;//缓冲区
private final Integer FULL=5;
final Lock lock =new ReentrantLock();//获得可重入锁
final Condition put =lock.newCondition();
final Condition out =lock.newCondition();
public static void main(String[] args){
TestReentrantLock t=new TestReentrantLock();
new Thread(t.new Producer()).start();
new Thread(t.new Consumer()).start();
new Thread(t.new Producer()).start();
new Thread(t.new Consumer()).start();
}
//生产者
class Producer implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
lock.lock();
try{
while(count==FULL){
try {
put.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
count++;
System.out.println("生产者"+Thread.currentThread().getName()+"已生产完成,商品数量:"+count);
out.signal();//通知消费之,现在可以消费
}finally{
lock.unlock();
}
}
}
}
class Consumer implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
lock.lock();
try{
while(count==0){
try {
out.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
count--;
System.out.println("消费者"+Thread.currentThread().getName()+"已消费,剩余商品数量:"+count);
}finally{
lock.unlock();
}
}
}
}
运行结果:
生产者Thread-0已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
4 BlockingQueue阻塞队列方法
BlockingQueue实现主要用于生产者-使用者队列,但它另外还支持Collection接口。是线程安全的,所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们 的目的。
BlockingQueue方法以四种形式出现,处于不能立即满足但可能在将来某一时刻可以满足的操作,这四中形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(null或者false,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。关于BlockingQueue更多介绍,请前往《BlockingQueue与CountDownLatch》
主要说说用于阻塞的两个方法:
* put()方法:将指定元素插入此队列中,将等待可用的空间(如果有必要)。
* take()方法:获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要的话)。
代码示例:
public class testBlockingQueue {
private static Integer count=0;
final BlockingQueue<Integer> bg=new ArrayBlockingQueue<>(5);
public static void main(String[] args){
testBlockingQueue te=new testBlockingQueue();
new Thread(te.new Producer()).start();
new Thread(te.new Consumer()).start();
new Thread(te.new Producer()).start();
new Thread(te.new Consumer()).start();
}
class Producer implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
bg.put(1);
count++;
System.out.println("生产者"+Thread.currentThread().getName()+"已生产完成,商品数量:"+count);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
bg.take();
count--;
System.out.println("消费者"+Thread.currentThread().getName()+"已消费,剩余商品数量:"+count);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
运行结果:
生产者Thread-0已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
消费者Thread-3已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
5 Semaphore 方法实现同步
信号量(semaphore)维护了一个许可集。在许可可用前会阻塞每一个acquire(),然后再获取该许可。每一个release()添加一个许可,从而可能释放一个正在阻塞的获取者,但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动。Semaphore通常用于限制可以访问某些资源(物理或逻辑)的线程数目。
注意,调用acquire()时无法保持同步锁,因为这会阻止该项返回到池中。信号量封装所需的同步,以限制对池的访问,这同维持该池本身一致性所需的同步是分开的。
代码实例:
public class testSemaphore {
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) {
// TODO Auto-generated method stub
testSemaphore te=new testSemaphore();
new Thread(te.new Producer()).start();
new Thread(te.new Consumer()).start();
new Thread(te.new Producer()).start();
new Thread(te.new Consumer()).start();
}
class Producer implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
put.acquire();//注意顺序
mutex.acquire();
count++;
System.out.println("生产者"+Thread.currentThread().getName()+"已生产完成,商品数量:"+count);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
mutex.release();
get.release();
}
}
}
}
class Consumer implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
get.acquire();
mutex.acquire();
count--;
System.out.println("消费者"+Thread.currentThread().getName()+"已消费,剩余商品数量:"+count);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
mutex.release();
put.release();
}
}
}
}
显示结果:
生产者Thread-0已生产完成,商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-2已生产完成,商品数量: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
生产者Thread-0已生产完成,商品数量:1
生产者Thread-2已生产完成,商品数量:2
消费者Thread-3已消费,剩余商品数量:1
消费者Thread-1已消费,剩余商品数量:0
生产者Thread-0已生产完成,商品数量:1
生产者Thread-2已生产完成,商品数量:2
消费者Thread-3已消费,剩余商品数量:1
消费者Thread-1已消费,剩余商品数量:0
6 PipedInputStream /PipedOutputStream
这个类位于java.io包中,是解决同步问题的最简单方法,一个线程将数据写入管道,另一个线程从管道读取数据,这样便构成了一种生产者/消费者的缓冲区编程模式。PipedInputStream/PipedOutputStream 只能用于多线程模式,用于单线程下可能会引发死锁。
代码实例:
public class testPiped {
final PipedInputStream pis=new PipedInputStream();
final PipedOutputStream pos=new PipedOutputStream();
public static void main(String[] args){
testPiped pi=new testPiped();
new Thread(pi.new Producer()).start();
new Thread(pi.new Consumer()).start();
}
class Producer implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
try {
pis.connect(pos);//连接输入池
} catch (IOException e) {
// TODO Auto-generated catch block
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
显示结果:
生产者Thread-0已生产完成,商品数量:134
生产者Thread-0已生产完成,商品数量:183
生产者Thread-0已生产完成,商品数量:160
生产者Thread-0已生产完成,商品数量:53
生产者Thread-0已生产完成,商品数量:136
生产者Thread-0已生产完成,商品数量:24
生产者Thread-0已生产完成,商品数量:161
生产者Thread-0已生产完成,商品数量:17
生产者Thread-0已生产完成,商品数量:103
生产者Thread-0已生产完成,商品数量:48
生产者Thread-0已生产完成,商品数量:23
生产者Thread-0已生产完成,商品数量:193
生产者Thread-0已生产完成,商品数量:35
生产者Thread-0已生产完成,商品数量:153
生产者Thread-0已生产完成,商品数量:108
生产者Thread-0已生产完成,商品数量:80
生产者Thread-0已生产完成,商品数量:82
生产者Thread-0已生产完成,商品数量:12
生产者Thread-0已生产完成,商品数量:204
生产者Thread-0已生产完成,商品数量:43
生产者Thread-0已生产完成,商品数量:226
生产者Thread-0已生产完成,商品数量:106
生产者Thread-0已生产完成,商品数量:71
消费者Thread-1已消费,剩余商品数量:205
消费者Thread-1已消费,剩余商品数量:86
消费者Thread-1已消费,剩余商品数量:231
消费者Thread-1已消费,剩余商品数量:105
消费者Thread-1已消费,剩余商品数量:108 .................
从结果上看出也可以实现同步,但一般不使用,因为缓冲区不易控制、数据不易封装和传输。