应用场景
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
分析
- 这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件
- 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费
- 对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费
- 在生产者消费者问题中,仅有synchronized是不够的。因为:
(1)synchronized可阻止并发更新同一个共享资源,实现了同步
(2)synchronized不能用来实现不同线程之间的消息传递(通信)
解决问题所需方法
线程之间通信需要等待和唤醒方法,而Object类提供了这些方法。以下方法都只能在同步方法或者同步代码块中使用,否则会抛出异常。
方法名 | 作用 |
---|---|
final void wait() | 表示线程一直等待,直到其它线程通知 |
final void wait(long timeout) | 指定等待的毫秒数 |
final void notifiy() | 唤醒一个处于等待状态的线程 |
final void notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先运行 |
- wait(): 执行后线程进入阻塞状态,会释放锁,即可让其它线程使用其之前占有的临界资源。
解决方法1:管程法
原理
- 生产者:负责生产数据的模块。(模块可以是方法、线程、进程、对象)
- 消费者:负责处理数据的模块。(模块可以是方法、线程、进程、对象)
- 缓冲区:消费者不能直接使用生产者的数据,它们之间有个“缓冲区”;生产者将生产好的数据放入缓冲区,消费者从缓冲区拿生产好的数据。缓冲区可以用队列实现,可放置多个数据。
模拟实现
code:
/**
* 模拟使用--管程法--解决生产者消费者问题
* 生产者:生产数据,多线程
* 消费者:使用数据,多线程
* 缓冲区容器:用于存放生产者生产的数据,提供消费者所需的数据(存、取操作), 容器有大小,
* 当缓冲区为空时,通知生产者生产;当容器满时,通知消费者使用(wait(), notify())
* 数据对象:生产者要生产的数据对象
* @author dxt
*
*/
public class CoTest01 {
public static void main(String[] args){
//缓冲区
SynContainer container = new SynContainer();
//一个 生产者
Producer p = new Producer(container);
//一个 消费者
Consumer c = new Consumer(container);
p.start();
c.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("100次存取数据操作完成!!!");
}
}
/**
* 生产者类
* @author dxt
*
*/
class Producer extends Thread{
SynContainer container;
public Producer(SynContainer container){
super();
this.container = container;
}
/**
* 重写run()方法
* 生产数据, 模拟使用100个数据
*/
public void run(){
//生产
for(int i=0; i<100; i++){
System.out.println("生产第" + i + "个面包");
container.push(new Bread(i));
}
}
}
/**
* 消费者类
* @author dxt
*
*/
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
super();
this.container = container;
}
/**
* 重写run()
*/
public void run(){
//消费, 模拟进行100次取数据操作
for(int i=0; i<100; i++){
System.out.println("消费第" + container.pop().id + "个面包");
//Bread b = container.pop();
}
}
}
/**
* 缓冲区
* @author dxt
*
*/
class SynContainer{
//缓冲区可存放10个数据
Bread[] breads = new Bread[10];
//计数器
int count = 0;
/**
* 存储 向缓冲区存放数据
* 加入synchronized,对调用push()方法的对象加锁,即对缓冲区加锁
* 当容器不满时就可进行生产
* @param b
*/
public synchronized void push(Bread b){
//没有空间
if(count == breads.length){
try {
this.wait(); //线层阻塞,通知消费者消费
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//存在空间 可以生产
breads[count] = b;
count++;
//存在数据,通知消费者可以消费
this.notifyAll();
}
/**
* 获取 消费
* 加入synchronized,对调用pop()方法的对象加锁,即对缓冲区加锁
* 要保证 当缓冲区有数据时 才能消费
* @return
*/
public synchronized Bread pop(){
//容器中有数据就可消费,没有数据就等待
if(count == 0){ //没有数据
try {
this.wait(); //线程阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//存在数据可以消费
count--;
Bread b = breads[count];
//有了空间后,通知生产者生产
this.notifyAll();
return b;
}
}
/**
* 面包数据
* @author dxt
*
*/
class Bread{
int id;
public Bread(int id){
super();
this.id = id;
}
}
解决方法2:信号灯法
模拟实现
code:
/**
* 模拟使用--信号灯法--解决生产者消费者问题
* @author dxt
*
*/
public class CoTest02 {
public static void main(String[] args){
Tv tv = new Tv();
new Player(tv).start();
new Watcher(tv).start();
}
}
/**
* 生产者
* @author dxt
*
*/
class Player extends Thread{
Tv tv;
public Player(Tv tv){
this.tv = tv;
}
public void run(){
//说20句话, 模拟产生20个数据
for(int i= 0; i<20; i++){
this.tv.play("第"+i+"句话");
}
}
}
/**
* 消费者
* @author dxt
*
*/
class Watcher extends Thread{
Tv tv;
public Watcher(Tv tv){
this.tv = tv;
}
public void run(){
for(int i = 0; i < 20; i++){
this.tv.watch();
}
}
}
class Tv{
//声音
String voice;
//信号灯:为真表示演员表演,观众等待;为假表示观众观看,演员等待
boolean flag = true;
/**
* 表演: 生产者生产数据
* @param voice
*/
public synchronized void play(String voice){
//演员等待
if(!flag){
try {
this.wait(); //演员等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//表演
this.voice = voice;
System.out.println("表演了" + this.voice);
//唤醒
this.notifyAll();
this.flag = !this.flag; //切换灯
}
/**
* 观看:消费者处理数据
*/
public synchronized void watch(){
//消费者等待
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//观看
System.out.println("听到了" + this.voice);
//唤醒
this.notifyAll();
this.flag = !this.flag; //切换灯
}
}