1、生产者-消费者模式概念
1.1重点
生产者如何安全的把数据交给消费者
1.2实际案例
蛋糕师傅:做蛋糕放在桌子上,如果桌子上放满了,师傅要等待,直到桌子上有空位才继续放蛋糕。
吃货:吃货吃桌子上的蛋糕,如果桌子上一个蛋糕也没有,吃货需要等待,直到有一个蛋糕位置。
桌子:放蛋糕,最多放n个蛋糕,取蛋糕和放蛋糕顺序一致
蛋糕:直接使用String
疑问:如何等待?如何唤醒?
等待:wait()
唤醒:notity(),notityAll();
需要创建的类
Table 放蛋糕 取蛋糕 MakerThread 先把蛋糕生产出来,调用Table的放蛋糕方法 Eatertread 调用Table的取蛋糕方法 Main 测试类
1.3编写代码前我们需要了解
Class ArrayBlockingQueue
- public class ArrayBlockingQueue
extends AbstractQueue
implements BlockingQueue, Serializable- 一个有限的blocking queue由数组支持。 这个队列排列元素FIFO(先进先出)。 队列的头部是队列中最长的元素。 队列的尾部是队列中最短时间的元素。 新元素插入队列的尾部,队列检索操作获取队列头部的元素。
- 这是一个经典的“有界缓冲区”,其中固定大小的数组保存由生产者插入的元素并由消费者提取。 创建后,容量无法更改。 尝试put成满的队列的元件将导致在操作阻挡; 尝试take从空队列的元件将类似地阻塞。
- 此类支持可选的公平策略,用于订购等待的生产者和消费者线程。 默认情况下,此订单不能保证。 然而,以公平设置为true的队列以FIFO顺序授予线程访问权限。 公平性通常会降低吞吐量,但会降低变异性并避免饥饿。
- 该类及其迭代器实现了
Collection
和Iterator
接口的所有可选方法。- 这个类是Java Collections Framework的成员。
- 从以下版本开始: 1.5
put(E e)方法
- 在该队列的尾部插入指定的元素,如果队列已满,则等待空间变为可用。
take()
- 检索并删除此队列的头,如有必要,等待元素可用。
sleep(long millis)
- 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
2、代码实现
2.1Table类-阻塞的两种实现方式
Table类将用两种方式实现,一是原生的写法,二是调用ArrayBlockingQueue来实现。
- Table原生实现
public class Table {
private String[] cakes; //选择使用数组来存放:1.需求上我们不需要扩充容量 2. 原生比包装的效率高
private int takeIndex; // 取蛋糕的索引
private int putIndex; // 放蛋糕的索引
private int count;// 蛋糕的数量
public Table(int count){
cakes = new String[count];
}
public synchronized void put(String cake) throws InterruptedException{
if(count >= cakes.length){
wait();
}
cakes[putIndex] = cake;
System.out.println(cake + " puts by: " + Thread.currentThread().getName());
putIndex = (putIndex + 1) % cakes.length;
count++;
notifyAll();
}
public synchronized String take() throws InterruptedException {
if(count <=0 ){
wait();
}
String cake = cakes[takeIndex];
System.out.println(cake + " takes by: " + Thread.currentThread().getName());
takeIndex = (takeIndex + 1) % cakes.length;
count--;
notifyAll();
return cake;
}
}
- 使用ArrayBlockingQueue来实现
//创建Table类继承ArrayBlockingQueue<E>类
public class Table extends ArrayBlockingQueue<String> {
//创建构造器
public Table(int count) {
super(count);
}
//创建put方法(放蛋糕)
public void put(String cake) throws InterruptedException {
super.put(cake);
System.out.println(cake);
}
//创建get方法(取蛋糕)
public String take() throws InterruptedException {
String cake = super.take();
System.out.println(cake + " 被" + Thread.currentThread().getName()+"吃了");
return cake;
}
}
2.2MakerThread类
public class MakerThread extends Thread {
private final Table table;
private String cake;
private static int id=1;
private Random random = new Random();
//创建构造器,对象名和table对象
public MakerThread(String name, Table table){
super(name);
this.table = table;
}
//重写run方法,实现放蛋糕
@Override
public void run() {
while (true){
String cake = "[" + getName() +"做了第"+generateId() + "个蛋糕]";
try {
table.put(cake);//放入蛋糕
Thread.sleep(random.nextInt(1000));//随机阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//生成蛋糕顺序id值
private static synchronized int generateId(){
return id++;
}
}
2.3 EaterThread类
//编写EaterThread类,实现拿蛋糕
public class EaterThread extends Thread{
private final Table table;
private Random random = new Random();
//编写构造器,将name,table装进对象里
public EaterThread(String name, Table table){
super(name);
this.table = table;
}
@Override
public void run() {
while(true) {
try {
String cake = table.take();//拿蛋糕
Thread.sleep(random.nextInt(1000));//阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.4编写Main测试类
public class Main {
public static void main(String[] args) {
Table table = new Table(5);
new MakerThread("一号小哥", table).start();
new MakerThread("二号小哥", table).start();
new MakerThread("三号小哥", table).start();
new MakerThread("四号小哥", table).start();
new EaterThread("1号小姐姐", table).start();
new EaterThread("2号小姐姐", table).start();
new EaterThread("3号小姐姐", table).start();
new EaterThread("4号小姐姐", table).start();
}
}
2.5运行结果:
[一号小哥做了第1个蛋糕]
[二号小哥做了第2个蛋糕]
[三号小哥做了第3个蛋糕]
[四号小哥做了第4个蛋糕]
[一号小哥做了第1个蛋糕] 被1号小姐姐吃了
[二号小哥做了第2个蛋糕] 被2号小姐姐吃了
[三号小哥做了第3个蛋糕] 被3号小姐姐吃了
[四号小哥做了第4个蛋糕] 被4号小姐姐吃了
[三号小哥做了第5个蛋糕]
[三号小哥做了第5个蛋糕] 被2号小姐姐吃了
[一号小哥做了第6个蛋糕]
[一号小哥做了第6个蛋糕] 被2号小姐姐吃了
2.6总结:线程的状态
- sleep方法是Thread类的静态方法,wait是Object的方法
- sleep不是必须要写在同步代码中,wait必须写在同步代码块中。
- sleep进入阻塞状态时,不会释放锁,wait进入阻塞中,会释放锁。
- wait可以通过notify/notifyAll方法,sleep到时候自己会醒来。