1.什么是生产者-消费者模式?
实际上是2类线程,一是生产者线程用于生产数据,二是消费者用于消费线程。生产者生产数据放置在共享数据区,并不需要关心消费者行为;而消费者只需要从共享数据库中拿数据消费。
因而这个共享数据区中的操作主要是:
1.当共享数据区已满,阻塞生产者继续生产放入共享数据区
2.当共享数据区为空,阻塞消费者消费数据
2.准备知识
问题可以转变为线程的并发通信问题
2.1Object.wait()和Object.notify(),
Object.wait()
将当前线程置入休眠状态,知道收到通知唤醒或被中断。在调用wait()之前,线程要获得当前对象的对象监视锁
(只能在同步方法或同步块中使用)。调用wait()后,线程会释放锁(Thread.sleep()不会)。但如果调用
wait()时线程未获得锁,会抛出IllegalMonitorStateException(RuntimeException)异常。如果再次获
得锁,线程才能从wait()方法处成功返回。
Object.notify()
该方法也要在同步方法或同步块中实现,在调用前,线程也要获得该对象的对象级别锁。同样,若调用notify()没有持有适当的锁,
也会抛出IllegalMonitorStateException。调用后,从WAITING状态中的线程台挑选一个进行通知,使得调用wait()的线程
从等待队列移入同步队列中,等待有机会获得锁,从而使该线程能够从wait()退出。调用notify后,当前线程不会马上释放锁,
要等到程序退出同步块后才会释放。
PS:notifyAll()使得所有原来在该对象wait()的线程统统退出WAITING状态,从等待队列加入同步队列中,等待获得monitor锁。
2.2Lock中condition的await()和singal
await()
当前线程进入WAITING状态,直到其他线程调用condition的singal或singalAll
singal
唤醒一个等待在condition上的线程,将该线程从等待队列中转移到同步队列,等待竞争锁
PS:singalAll等待竞争所有锁
2.3blockingQueue
内部实现附加了一个阻塞操作。当队列已满,阻塞向队列中插入数据的线程,直至线程中为空;当队列为空,阻塞从队列中获得数据的线程,直到线程不为空为止。
3.具体实现
3.1使用Object.wait和notify
package 多线程;
public class ProceduercConsumerDemo {
public static void main(String[] args) {
Resource resource = new Resource();
ConsumerThread consumerThread = new ConsumerThread(resource);
ProducerThread producerThread = new ProducerThread(resource);
Thread t0 = new Thread(consumerThread);
Thread t1 = new Thread(consumerThread);
Thread t2 = new Thread(producerThread);
Thread t3 = new Thread(producerThread);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
class ConsumerThread implements Runnable {
private Resource resource;
public ConsumerThread(Resource resource) {
super();
this.resource = resource;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
resource.remove();
}
}
}
class ProducerThread implements Runnable {
private Resource resource;
public ProducerThread(Resource resource) {
super();
this.resource = resource;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
resource.add();
}
}
}
class Resource {
// 当前资源数
private int num = 0;
// 允许存放的资源数
private int size = 10;
// 生产
public synchronized void add() {
if (num == 0) {
num++;
System.out.println("生产" + Thread.currentThread());
notifyAll();
} else {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
// 消费
public synchronized void remove() {
if (num > 0) {
num--;
System.out.println("消费" + Thread.currentThread().getName());
notify();// 通知生产
} else {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
3.2使用condition的await和singal
package 多线程;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* https://blog.csdn.net/javazejian/article/details/75043422
* @author yinjialing
* 一个锁有多个等待队列
*
*/
public class ResourceByCondition {
private String name;
private int count=1;
// true:生成完毕,false:消费完毕
private boolean flag=true;
Lock lock=new ReentrantLock();
//监视器--监视生产者
Condition producer_con=lock.newCondition();
//监视器--监视消费者
Condition consumer_con=lock.newCondition();
//当flag为true,表示数据生成完成,那么就让监视生产者的监视器进入等待队列
//否则,生产一个数据,将flag设置为true,并唤醒消费者
public void product(String name) {
lock.lock();
try {
while(flag)
try {
producer_con.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.name=name+count;
count++;
System.out.println(Thread.currentThread().getName()+"-生产者-"+this.name);
flag=true;
//唤醒消费者
consumer_con.signal();
}finally {
lock.unlock();
}
}
//当flag为false,表示没,那么就让监视消费者的监视器进入等待队列
//否则,消费一个数据,将flag设置为false,并唤醒生产者
public void consume() {
lock.lock();
try {
while(!flag)
try {
consumer_con.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-生产者-"+this.name);
flag=false;
//唤醒生产者
producer_con.signal();
}finally {
lock.unlock();
}
}
}
package 多线程;
public class ResourceByConditionDemo {
public static void main(String[] args) {
ResourceByCondition r=new ResourceByCondition();
ProducerThread pro=new ProducerThread(r);
ConsumerThread con=new ConsumerThread(r);
//2个生产者线程
Thread t0=new Thread(pro);
Thread t1=new Thread(pro);
//2个消费者线程
Thread t2=new Thread(con);
Thread t3=new Thread(con);
t0.start();t1.start();t2.start();t3.start();
}
}
/**
*
* @author yinjialing
* 生产者
* 一直生产
*/
class ProducerThread implements Runnable{
private ResourceByCondition r;
public ProducerThread(ResourceByCondition r) {
super();
this.r = r;
}
public void run() {
while(true) {
r.product("生产");
}
}
}
/**
* 消费者
* 一直消费
* @author yinjialing
*
*/
class ConsumerThread implements Runnable{
private ResourceByCondition r;
public ConsumerThread(ResourceByCondition r) {
super();
this.r = r;
}
public void run() {
while(true) {
r.consume();
}
}
}
3.3使用阻塞队列做共享数据区
package 多线程;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class ArrayBlockingQueueDemo {
private final static ArrayBlockingQueue<String> queue=new ArrayBlockingQueue<>(1);
public static void main(String[] args) {
new Thread(new Producer(queue)).start();
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}
class Consumer implements Runnable{
private ArrayBlockingQueue<String> arrayBlockingQueue;
public Consumer(ArrayBlockingQueue<String> arrayBlockingQueue) {
this.arrayBlockingQueue = arrayBlockingQueue;
}
@Override
public void run() {
while(true) {
try {
TimeUnit.MILLISECONDS.sleep(1000);consume();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void consume() throws InterruptedException {
String str=arrayBlockingQueue.take();
System.out.println("消费"+str);
}
}
class Producer implements Runnable{
private ArrayBlockingQueue<String> arrayBlockingQueue;
public Producer(ArrayBlockingQueue<String> arrayBlockingQueue) {
this.arrayBlockingQueue = arrayBlockingQueue;
}
@Override
public void run() {
while(true) {
produce();
}
}
private void produce() {
String str="Producer";
try {
arrayBlockingQueue.put(str);
System.out.println("生产"+str);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
4.其他联想到的问题
2个线程打印奇偶数也是这样的模式(除了阻塞队列),也可用automicInteger