8.线程通信问题
(1)生产者和消费者问题
线程协作-生产者消费者模式:
- 应用场景:生产者和消费者问题
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。
Producer(生产者) -> 数据缓存区 -> Consumer(消费者) |
---|
(2)生产者和消费者问题-分析
线程通信-分析
这是一个线程同步的问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件!
-
对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费;
-
对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费;
-
在生产者消费者问题上,仅有
synchronized
是远远不够的:synchronized
可阻止并发更新同一个共享资源,实现了同步synchronized
不能用来实现不同线程之间的消息传递(通信)
-
Java 提供了几个方法用于解决线程之间的通信问题
方法名 作用 wait() 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 wait(long timeout) 指定等待的毫秒数 notify() 唤醒一个处于等待状态的线程 notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 注意:
均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常
IllegaiMonitorStateException
(3)生产者和消费者问题-解决方式
解决方式一:
并发协作模型之 " 生产者 / 消费者模式 " --> 管程法
- 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
- 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
- 缓冲区:消费者不能直接使用生产者的数据,他们之间有个 " 缓冲区 "
生产者将生产好的数据放入缓冲区,消费者 从缓冲区拿出数据!
解决方式二:
并发协作模型之 " 生产者 / 消费者模式 " -- > 信号灯法(标志位)
方式1:管程法
代码实现:
//解决线程通信方式-生产者与消费者:管程法(P-缓存-C)
//生产者,消费者,缓存区,产品
public class TestCache {
public static void main(String[] args) {
Cache cache = new Cache();
new Producer(cache).start();
new Consumer(cache).start();
}
}
// 产品
class Chicken{
int id;
Chicken(int id){
this.id = id;
}
}
// 生产者
class Producer extends Thread {
Cache cache;
public Producer(Cache cache) {// 一定要注意不能丢
this.cache = cache;
}
// 生产者生产
@Override
public void run() {
for (int i = 1; i < 20; i++) {
cache.push(new Chicken(i));
System.out.println("生产了" + i +"只鸡");
}
}
}
// 消费者
class Consumer extends Thread {
Cache cache;
public Consumer(Cache cache) {// 一定要注意不能丢
this.cache = cache;
}
// 消费者消费
@Override
public void run() {
for (int i = 1; i < 20; i++) {
Chicken pop = cache.pop();
System.out.println("消费者消费第"+pop.id+"只鸡");
}
}
}
// 缓存区
class Cache{
// 需要一个容器大小
Chicken[] chickens = new Chicken[10];
// 容器计数器
int count = 0;
// 生产者放入产品
public synchronized void push(Chicken chicken){
// 如果容器满了,则等待消费者进行消费
if (count == chickens.length) {
try {
// 生产者等待
this.wait();
System.out.println(">>>>>>>消费者等待...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果没有满,则生产者需要放入产品
chickens[count] = chicken;
count++;
// 如果有产品了,可以通知消费者进行消费
this.notifyAll();
}
// 消费者取产品
// 注意使用wait()、notifyAll()方法,必须要有synchronized关键字修饰方法!
public synchronized Chicken pop(){
// 判断能否消费
if (count == 0) {
try {
// 消费者等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果有产品,可以进行消费
count--;
Chicken chicken = chickens[count];
// 吃完了及时通知生产者生产
this.notifyAll();
return chicken;
}
}
执行结果:
生产了1只鸡
生产了2只鸡
...
生产了10只鸡
>>>>>>>消费者等待...
消费者消费第10只鸡
生产了11只鸡
生产了12只鸡
消费者消费第11只鸡
>>>>>>>消费者等待...
生产了13只鸡
消费者消费第12只鸡
消费者消费第13只鸡
>>>>>>>消费者等待...
生产了14只鸡
生产了15只鸡
消费者消费第14只鸡
消费者消费第15只鸡
>>>>>>>消费者等待...
生产了16只鸡
生产了17只鸡
消费者消费第16只鸡
消费者消费第17只鸡
>>>>>>>消费者等待...
生产了18只鸡
消费者消费第18只鸡
生产了19只鸡
消费者消费第19只鸡
消费者消费第9只鸡
...
消费者消费第1只鸡
方式2:信号灯法
//解决线程通信方式-生产者与消费者:信号灯法(标志位)
//生产者,消费者,标志位,产品
public class TestFlag {
public static void main(String[] args) {
Tv tv =new Tv();
new Player(tv).start();
new Viewer(tv).start();
}
}
// 节目(产品)
class Tv{
//演员表演,观众等待 T
//观众观看,演员等待 F
String voice;//表演节目
boolean flag = true;// 标志位
// 表演者表演
public synchronized void play(String voice){
// 如果为标志为为false,演员等待
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了:"+voice);
// 如果标志位为true,则通知观众观看
this.notifyAll();
// 一定要注意修改标志位
this.voice = voice;// 添加节目
this.flag = !this.flag; // 修改标志位
}
// 观众观看
public synchronized void watch(){
// 如果为标志为为true,观众等待
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看完了:" + voice);
// 一定要注意修改标志位
// 如果标志位为false,则通知演员演出
this.notifyAll();
this.flag = !this.flag; // 修改标志位
}
}
// 表演者(生产者)
class Player extends Thread{
Tv tv;
public Player(Tv tv){
this.tv = tv;
}
// 表演者只管表演
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i%2 == 0) {
this.tv.play("射雕英雄传!");
} else {
this.tv.play("CSDN看技术文章!");
}
}
}
}
// 观众(消费者)
class Viewer extends Thread {
Tv tv;
public Viewer(Tv tv){
this.tv = tv;
}
// 观众只管看
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.tv.watch();
}
}
}
执行结果
演员表演了:射雕英雄传!
观众观看完了:射雕英雄传!
演员表演了:CSDN看技术文章!
观众观看完了:CSDN看技术文章!
演员表演了:射雕英雄传!
观众观看完了:射雕英雄传!
演员表演了:CSDN看技术文章!
观众观看完了:CSDN看技术文章!
演员表演了:射雕英雄传!
观众观看完了:射雕英雄传!