前言
多线程的学习接近尾声了,这次我们的主题是线程通信
一、线程通信概述
通过加锁的方式保证共享数据访问的完整性,但是并没有规定线程执行的先后顺序。各线程到底谁先执行由操作系统的调度决定
在进行多线程的设计时,还会遇到另一类问题:如何控制相互交互的线程之间的运行顺序,及多线程的同步
二、典型例子
1.生产者和消费者例子
生产者和消费者的问题
1、生产者:先看是否有数据,如果有就等待;如果没有就生产,生产之后通知消费者来消费
2、消费者:先看是否有数据,如果有就消费;如果没有就等待,通知生产者生产数据
2.通过例子理解与剖析
1、线程的安全问题
因为生产者与消费者共享数据缓冲区,不过这个问题可以使用同步解决
2、线程协调问题
要解决问题,就必须让生产者线程在缓冲区满时等待(wait),暂停进入阻塞状态,等下次消费者消耗了缓冲区中的数据的时间,通知(notify)正在等待的线程恢复到就绪状态,重新开始往缓冲区添加数据。同样,也可以让消费者线程在缓冲区进入等待(wait),暂停进入阻塞状态,等到生产者往缓冲区添加数据之后,再通知(notify)正在等待的线程恢复到就绪状态。
三、常用API
wait() | 使用当前线程放弃同步锁并进入到等待,直到其他线程进入此同步锁并调用notify()或notifyAll()方法唤醒该线程为止 |
---|---|
notify() | 唤醒此同步锁上等待的第一个调用wait()方法的线程 |
notifyAll() | 唤醒此同步锁上调wait()方法的所有线程 |
注意:
wait()、notify()、notifyAll()这三个方法的调用者都应该是同步锁对象;否则就会报异常
四、生产者和消费者例子代码实现
代码如下(银行卡类):
public class Card {
// 创建number、money、message三个私有属性
private String number;
private int money;
private String message;
// 无参构造函数
public Card() {
}
// 有参构造函数
public Card(String number, int money, String message) {
super();
this.number = number;
this.money = money;
this.message = message;
}
// 三个属性get set 方法
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
// 重写toString()方法
@Override
public String toString() {
return "Card [number=" + number + ", money=" + money + ", message=" + message + "]";
}
}
代码如下(生产者线程):
public class ProducerRunnable implements Runnable {
private Card card;
private int counter = 0;
public ProducerRunnable(Card card) {
this.card = card;
}
@Override
public void run() {
while(true) {
synchronized(card) {
int money = card.getMoney();
if(money<=0) {
if(counter%2 ==0) {
card.setMessage("dad set money");
card.setMoney(3000);
}else {
card.setMessage("mom set money");
card.setMoney(5000);
}
counter++;
//唤醒等待的线程
card.notify();
}else {
try {
// 进入等待
card.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
代码如下(消费者线程):
public class ConsumerRunnable implements Runnable {
private Card card;
public ConsumerRunnable(Card card) {
this.card = card;
}
@Override
public void run() {
while(true) {
synchronized(card) {
int money = card.getMoney();
if(money>0) {
String message = card.getMessage();
System.out.println("取钱:"+message+" "+money);
//取钱后将银行卡余额设置为0
card.setMoney(0);
//唤醒等待的线程
card.notify();
}else {
try {
// 进入等待
card.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
代码如下(测试):
public class Test01 {
public static void main(String[] args) {
//创建银行卡对象
Card card = new Card("6548755", 0, "success");
// 获取消费者和生产者类
ProducerRunnable producerRunnable = new ProducerRunnable(card);
ConsumerRunnable consumerRunnable = new ConsumerRunnable(card);
// 获取消费者和生产者类对应的线程类
Thread producerthread = new Thread(producerRunnable);
Thread consumerthread = new Thread(consumerRunnable);
//启动线程
producerthread.start();
consumerthread.start();
}
}
代码如下(输出):
取钱:mom set money 5000
取钱:dad set money 3000
取钱:mom set money 5000
取钱:dad set money 3000
取钱:mom set money 5000
取钱:dad set money 3000
取钱:mom set money 5000
取钱:dad set money 3000
取钱:mom set money 5000
取钱:dad set money 3000
取钱:mom set money 5000
取钱:dad set money 3000
取钱:mom set money 5000
取钱:dad set money 3000
取钱:mom set money 5000
取钱:dad set money 3000
取钱:mom set money 5000
取钱:dad set money 3000
总结
输出只是截取了一部分,这个例子一是加强对wait()、notify()的理解和运用,再就是加强对也锁在多线程里的角色担当。