线程的通信【生产者与消费者设计模式】
1.1 线程通信
需求:你和你朋友公用一张银行卡,你向卡中存钱,你朋友取钱,保证你存一笔,然后取一笔,再存一笔,再取一笔。
实现功能:使用线程通信。
在jdk1.5之前有三个方法实现线程通信:
wait(): 等待,线程执行这个方法进入等待队列(和锁有关,一个锁对应一个等待队列), 需要被唤醒
notify(): 通知唤醒,从等待队列中随机唤醒一个线程
notifyAll():全部唤醒,把等待队列中所有的线程都唤醒
代码实现
public class BankCard {
private double money;
private boolean flag;// 标记 true 表示有钱, false没钱
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
/***
* 存取
*/
public synchronized void save() { //this
while(flag) {//true 有钱
try {
this.wait();//等待,释放了cpu,释放了锁 ,调用wait的对象是锁
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//等待
}
money=money+1000;
System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+money);
flag=true;//修改标记
this.notifyAll();//唤醒取钱线程取取钱
}
/**
* 取
*/
public synchronized void qu() {//this
while(flag==false) {
try {
this.wait();//等待 释放cpu和锁
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
money=money-1000;
System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+money);
//修改标记
flag=false;
//唤醒
this.notifyAll();
}
}
/**
* 存取类
* @author wgy
*
*/
public class AddMoney implements Runnable{
private BankCard card;
public AddMoney(BankCard card) {
this.card = card;
}
@Override
public void run() {
for(int i=0;i<10;i++) {
card.save();
}
}
}
public class SubMoney implements Runnable{
private BankCard card;
public SubMoney(BankCard card) {
this.card = card;
}
@Override
public void run() {
for(int i=0;i<10;i++) {
card.qu();
}
}
}
public class Test {
public static void main(String[] args) {
//1创建卡
BankCard card=new BankCard();
//2创建存钱和取钱功能
AddMoney add=new AddMoney(card);
SubMoney sub=new SubMoney(card);
//3创建线程对象
Thread zhengshuai=new Thread(add,"帅帅");
Thread dalang=new Thread(add, "大郎");
Thread benwei=new Thread(sub,"本伟");
Thread xiaolian=new Thread(sub,"金莲");
//4启动
zhengshuai.start();
benwei.start();
dalang.start();
xiaolian.start();
}
}
1.2 生产者与消费者设计模式原理
它描述的是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者可以从仓库中取走产品,解决生产者/消费者问题,我们需要采用某种机制保护生产者和消费者之间的同步
同步问题核心在于:如何保证同一资源被多个线程并发访问时的完整性,常用的方法就是加锁,保证资源在任意时刻只被一个线程访问
画图分析:
[外链图片转存失败(img-YooFqDLM-1565568932814)(生产者与消费者设计模式.png)]
1.3 实现
采用wait()、notify()和notifyAll()方法
wait():当缓冲区已满或空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等待状态,让其他线程执行
·是Object的方法
·调用方式:对象.wait();
·表示释放 对象 这个锁标记,然后在锁外边等待(对比sleep(),sleep是抱着锁休眠的)
·等待,必须放到同步代码段中执行
notify():当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态
·是Object的方法
·调用方式:对象.notify();
·表示唤醒 对象 所标记外边在等待的一个线程
notifyAll():全部唤醒
·是Object的方法
·调用方式:对象.notifyAll()
·表示唤醒 对象 所标记外边等待的所有线程
/*
*面包
*/
public class Bread {
private int id;
private String productName;
public Bread() {
// TODO Auto-generated constructor stub
}
public Bread(int id, String productName) {
super();
this.id = id;
this.productName = productName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
@Override
public String toString() {
return "Bread [id=" + id + ", productName=" + productName + "]";
}
}
/*
* 面包容器
*/
public class BreadCon {
private Bread con;
private boolean flag; //
//放入面包
public synchronized void input(Bread b){
while(flag==true){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.con=b;
System.out.println(Thread.currentThread().getName()+"生产了"+b.getId());
flag=true;
this.notifyAll();
}
//吃面包
public synchronized void output(){
while(flag==false){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Bread b=con;
con=null;
System.out.println(Thread.currentThread().getName()+"消费了"+b.getId()+" 生产者:"+b.getProductName());
flag=false;
this.notifyAll();
}
}
/*
* 生产面包类
*/
public class Product implements Runnable{
private BreadCon con;
public Product(BreadCon con) {
this.con=con;
}
@Override
public void run() {
for(int i=1;i<=30;i++){
Bread b=new Bread(i, Thread.currentThread().getName());
this.con.input(b);
}
}
}
/*
* 消费面包
*/
public class Consume implements Runnable{
private BreadCon con;
public Consume(BreadCon con) {
this.con=con;
}
@Override
public void run() {
for(int i=1;i<=30;i++){
con.output();
}
}
}
public static void main(String[] args) {
//1创建容器
BreadCon con=new BreadCon();
//2生产
Product product=new Product(con);
//3消费
Consume consume=new Consume(con);
//4线程对象
Thread shaqiang=new Thread(product, "莎强");
Thread xiaocang=new Thread(consume,"小苍");
//5启动
shaqiang.start();
xiaocang.start();
}
扩展:使用Jdk1.5 Lock优化生产者和消费者
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 面包容器
*/
public class BreadCon {
private Bread con;
private boolean flag;
private Lock lock=new ReentrantLock();
Condition proCondition=lock.newCondition();
Condition conCondition=lock.newCondition();
/**
* 放入面包
*/
public void input(Bread b) {
lock.lock();
try {
while (flag) {
try {
proCondition.await();
} catch (Exception e) {
// TODO: handle exception
}
}
con = b;
System.out.println(Thread.currentThread().getName() + "生产了" + b.getId() + "面包");
flag = true;
conCondition.signal();
} finally {
lock.unlock();
}
}
/**
* 消费面包
*/
public void output() {
lock.lock();
try {
while (!flag) {
try {
conCondition.await();
} catch (Exception e) {
// TODO: handle exception
}
}
Bread b = con;
System.out
.println(Thread.currentThread().getName() + "消费了" + b.getId() + "面包, 生产者名字:" + b.getProductName());
con = null;
flag = false;
proCondition.signal();
} finally {
lock.unlock();
}
}
}