线程通信
应用场景:生产者和消费者问题
• 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
• 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
• 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
分析
• 这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件
• 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费
• 对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费
• 在生产者消费者问题中,仅有synchronized是不够的
• synchronized可阻止并发更新同一个共享资源,实现了同步
• synchronized不能用来实现不同线程之间的消息传递(通信)
Java提供了3个方法解决线程之间的通信问题
线程通信解决方式一:
并发协作模型“生产者/消费者模式” ——》管程法
生产者: 生产者指的是负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)
消费者: 消费者指的是负责处理数据的模块(这里模块可能是:方法、对象、线程、进程)
缓冲区: 消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据
缓冲区是实现并发的核心,缓冲区的设置有3个好处:
实现线程的并发协作
有了缓冲区以后,生产者线程只需要往缓冲区里面放置数据,而不需要管消费者消费的情况;同样,消费者只需要从缓冲区拿数据处理即可,也不需要管生产者生产的情况。 这样,就从逻辑上实现了“生产者线程”和“消费者线程”的分离。
解耦了生产者和消费者
生产者不需要和消费者直接打交道。
解决忙闲不均,提高效率
生产者生产数据慢时,缓冲区仍有数据,不影响消费者消费;消费者处理数据慢时,生产者仍然可以继续往缓冲区里面放置数据
协作模型:生产者消费者实现方式一:管程法 借助缓冲区
/**
* 协作模型:生产者消费者实现方式一:管程法
* 借助缓冲区
* @author Administrator
*
*/
public class CoTest01 {
public static void main(String[] args) {
SynContainer container=new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container) {
this.container = container;
}
@Override
public void run() {
//生产
for(int i=1;i<=20;i++) {
System.out.println("生产第--》"+i+"个馒头");
container.push(new Steamedbun(i));
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
public void run() {
//消费
for(int i=1;i<=20;i++) {
System.out.println("消费第-->"+container.pop().id+"个馒头");
}
}
}
//缓冲区
class SynContainer{
Steamedbun[] buns=new Steamedbun[10]; //存储容器
int count=0; //计数器
// 存储 生产
public synchronized void push(Steamedbun bun) {
//何时能生产 容器存在空间
//不能生产 只能等待
if(count == buns.length) {
System.out.println("------"+buns.length+"-----------");
try {
this.wait(); //线程阻塞 消费者通知生产解除
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//存在空间 可以生产
// //先指向空的,再放东西进去。 放一个东西,计数加1 这样做是错误的,因为导致count计数不一致
// count++;
// buns[count]=bun;
buns[count]=bun;
count++;
//存在数据了,可以通知消费啦
this.notifyAll();
}
//获取 消费
public synchronized Steamedbun pop() {
//何时消费 容器中是否存在数据
//没有数据 只有等待
if(count==0) {
try {
this.wait(); //线程阻塞 生产者通知消费解除
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//存在数据可以消费
count--;
Steamedbun bun=buns[count];
this.notifyAll(); //存在空间了,可以唤醒对方生产了
return bun;
}
}
//馒头
class Steamedbun{
int id;
public Steamedbun(int id) {
this.id=id;
}
}
为什么有这种情况????