Java------多线程_并发与协作_生产者消费者模式(十二)
线程与线程之间如何通讯?
使用消息队列进行通讯。
线程通信:
应用场景:生产者和消费者问题
生产者和消费者共享同一个资源,并且生产者和消费者之间互相依赖,互为条件。
1.对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费
2.对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费
3.生产者消费者问题中,仅有synchronized是不够的
(1)synchronized可阻止并发更新同一个共享资源,实现了同步
(2)synchronized不能用来实现不同线程之前的消息传递(通信)
解决方式1:并发协作模型“生产者/消费者模式”-》管程法
1.生产者:负责生产数据的模块
2.消费者:负责处理数据的模块
3.缓冲区:消费者不能直接使用生产者的数据,它们之间有个“缓冲区”
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿要处理的数据。
优点:生产者与消费者解耦,提高效率,利用管道
buffer(共享内存)
解决方式2:并发协作模型“生产者/消费者模式”-》信号灯法
Java提供三个方法解决线程之间的通信
1.final void wait() 、 作用:表示导致当前线程一直等待,直到其他线程通知notify、或者notifyAll,与sleep不同,会释放锁。
2.final void wait(long timeout)、作用:指定等待的毫秒数
3.final void notify() 、 作用:唤醒一个处于等待状态的线程
4.final void notifyAll() 、 作用:唤醒一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度。
以上方法都是java.lang.Object类的方法都只能在同步方法或者同步代码块中使用,否则会抛出异常。
管程法:
生产者和消费者都是多线程,缓冲区采用容器(存数据、取数据)、数据。
共四种角色。
容器存:当容器没有空间了,通知消费者等待
容器取:当容器空了,通知消费者生产
管程法案例代码:【运行代码时,存在一个问题,在存储数据过程中,会爆数组越界,把异常打印注释掉后,程序可以正常运行】
一边存数据,一边去数据,当缓冲区数据为空时,消费者wait,当缓冲区数据满时,生产者wait。
package cooperation;
/**
* 协作模型:生产者消费者实现方式一:管程法
* 借助缓冲区
*/
public class CoTest {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
new Productor(synContainer).start();
new Consumer(synContainer).start();
}
}
//生产者
class Productor extends Thread{
SynContainer container;
//构造方法
public Productor(SynContainer container){
this.container = container;
}
@Override
public void run() {
//生产
for (int i=0;i<10;i++){
System.out.println("生产第---"+i+"个元素");
container.push(new MDate(i));
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
//构造方法
public Consumer(SynContainer container){
this.container = container;
}
//消费
@Override
public void run() {
for (int i=0;i<1000;i++){
System.out.println("消费第----"+container.get().id+"个元素");
}
}
}
//缓冲区
class SynContainer{
//存储容器
MDate[] mDates = new MDate[10];
//计数器
int count = 0;
//存
public synchronized void push(MDate mDate){
//何时能生产,容器存在空间,可以生产
//不能生产
if (count == mDates.length){
try {
this.wait(); //线程阻塞,消费者通知生产者生产
} catch (InterruptedException e) {
// e.printStackTrace();不注释会报错
}
}
mDates[count] = mDate;
count++;
this.notifyAll();//存在空间,通知消费者开始生产
}
//取
public synchronized MDate get(){
//何时消费
if (count == 0){
try {
this.wait(); //此时线程阻塞,生产者通知消费解除阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
MDate mDate = mDates[count];
this.notifyAll();//存在空间唤醒生产者生产
return mDate;
}
}
//数据
class MDate{
//编号
int id;
public MDate(int id){
this.id = id;
}
}
信号灯法:红灯停、绿灯行,通过真假标识。
演员表演什么,观众才能听到什么
package cooperation;
/**
* 协作模型:生产者消费者实现方式一:信号灯法
* 借助标志位
*/
public class CoTest01 {
public static void main(String[] args) {
Tv Tv = new Tv();
new Player(Tv).start();
new Watcher(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++){
if(i%2 == 0){
this.tv.play("奇葩说");
}else {
this.tv.play("火星情报局");
}
}
}
}
//消费者 观众
class Watcher extends Thread{
Tv tv;
//构造方法
public Watcher(Tv tv){
this.tv = tv;
}
//消费
@Override
public void run() {
for (int i=0;i<20;i++){
this.tv.watch();
}
}
}
//同一个资源电视
class Tv{
String voice;
//信号灯
//true 表示演员表演,观众等待
//false 表示观众观看,演员等待
boolean flag = true;
//表演
public synchronized void play(String voice){
//演员等待
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("表演了:"+voice);
this.voice = voice;
this.notifyAll();
this.flag = !this.flag;
}
//观看
public synchronized void watch() {
//观众等待
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("听到了:"+voice);
this.notifyAll();
this.flag = !this.flag;
}
}