生产者与消费者问题
场景举例:假设仓库中只能放一件商品,生产者负责生产商品,消费者负责消费。如果仓库中没有产品,生产者就要继续生产,而消费者等待。仓库中若有商品,那么生产者等待,消费者进行消费
生产者对应着产生数据的模块,消费者对应着处理数据的模块,而仓库就是缓冲区。
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者相互依赖,互为条件。此时仅仅用synchronized是不够的,因为不仅要同步,解决并发,还要实现线程间的通信。
以下是相关的java方法,且都是Object方法。因为所有对象都有锁,因此这些方法在Object类中就有,且这些方法只能在同步方法或者同步块中使用
方法名 | 作用 |
---|---|
wait() | 线程一直等待,知道其他线程通知,且会释放锁(与sleep不一样) |
wait(long timeout) | 等待制定毫秒数 |
notify() | 唤醒一个处于等待状态的线程(唤醒谁看CPU) |
notifyAll() | 唤醒同一个对象上调用了wait()方法的所有线程 |
对于生产者消费者问题,有两种解决方法:管程法和信号灯法
管程法
缓冲区、产品、生产者、消费者
缓冲区满了,让生产者等待;空了,就让消费者等待。
生产者生产好了,就唤醒消费者。消费者消费完了,就唤醒生产者
下面例子中,由于我们的缓冲区大小设置为10,不是1。因此会发生生产者连续生产好几个,消费者才进行消费。但生产者每次生产完,都得去唤醒消费者
public class TestPC {
public static void main(String[] args) {
//创建缓冲区
Huanchongqu container = new Huanchongqu();
//创建并启动生产者和消费者线程
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread{
Huanchongqu container;
public Productor(Huanchongqu container) {
this.container = container;
}
@Override
public void run() {
for (int i = 1; i < 21; i++) {
System.out.println("生产了第"+i+"只鸡");
//生产好后就准备往里面放,放不进去就等待,放进去就继续生产
container.push(new Chicken(i));
}
}
}
//消费者
class Consumer extends Thread{
Huanchongqu container;
public Consumer(Huanchongqu container) {
this.container = container;
}
@Override
public void run() {
for (int i = 1; i < 21; i++) {
Chicken chicken = container.pop();
System.out.println("消费第"+ i + "只鸡");
}
}
}
//产品
class Chicken {
int id;
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class Huanchongqu{
//放产品的容器
Chicken[] chickens = new Chicken[10];
//计数器
int count = 0;
//生产者放入产品
public synchronized void push(Chicken chicken){
//没位置就等待
if (count == chickens.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有位置就被唤醒
chickens[count] = chicken;
count++;
System.out.println("第"+chicken.id+"只鸡被摆上 "+"台上有"+count+"只鸡");
//唤醒消费者消费
this.notifyAll();
}
//消费者使用产品
public synchronized Chicken pop() {
//没产品就等待
if(count <= 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有产品那就消费
count--;
Chicken chicken = chickens[count];
System.out.println("消费后,台上有"+count+"只鸡");
//唤醒生产者生产
this.notifyAll();
return chicken;
}
}
信号灯法
设置一个标志位,true或者false。true的时候,让生产者停止;false的时候,让消费者停止。生产者生产完或者消费者消费完,就将true反转。
举例:例子有点牵强,TV、演员和观众。演员拍戏时,观众停止观看,观众观看时,演员停止拍戏。
public class TestPC2 {
public static void main(String[] args) {
Tv tv = new Tv();
new player(tv).start();
new guanzhong(tv).start();
}
}
//演员
class player extends Thread{
Tv tv;
public player(Tv tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.play("快乐大本营");
}
}
}
//观众
class guanzhong extends Thread{
Tv tv;
public guanzhong(Tv tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.guankan();
}
}
}
//电视
class Tv {
//flag为true时,演员演,观众等待
//flag为false时,观众看,演员等待
boolean flag = true;
String voice;
//演员表演
public synchronized void play(String voice){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.voice = voice;
System.out.println("正在演"+voice);
flag = !flag;
this.notify();
}
//观众观看
public synchronized void guankan(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众正在收看"+voice);
flag = !flag;
this.notify();
}
}