等待唤醒机制
生产者和消费者模式
生产者消费者模式是一个十分经典的多线程协作的模式。
消费者等待
生产者等待
常见方法
方法名称 | 说明 |
void wait() | 当前线程等待,直到被其他线程唤醒 |
void notify() | 随机唤醒单个线程 |
void notifyAll() | 唤醒所有线程 |
厨子生产数据(生产者):
// 厨子
public class Cook extends Thread{
@Override
public void run() {
/*
* 1.循环
* 2.同步代码块
* 3.判断共享数据是否到了末尾(到了末尾)
* 4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
*/
while (true){
synchronized (Desk.lock){
if (Desk.count == 0){
// 判断吃货是否还能吃得下
// 吃不下,跳出循环
break;
}else {
// 如果有,就等待
if(Desk.foodFlag == 1){
// 如果有,就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
// 如果没有,就制作食物
System.out.println("厨师做了一碗面条");
// 修改桌子上的食物状态
Desk.foodFlag = 1;
// 叫醒等待的消费者开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
吃货消费数据(消费者):
// 吃货
public class Foodie extends Thread{
@Override
public void run() {
/*
* 1.循环
* 2.同步代码块
* 3.判断共享数据是否到了末尾(到了末尾)
* 4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
*/
while (true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else {
// 先判断桌子上是否有面条
if (Desk.foodFlag == 0){
// 如果没有,就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
// 把吃的总数-1
Desk.count--;
// 如果有,就开吃
System.out.println("吃货在吃面条,还能再吃" + Desk.count + "碗!");
// 吃完之后,唤醒厨师(生产者)
Desk.lock.notifyAll();
// 修改桌子的状态
Desk.foodFlag = 0;
}
}
}
}
}
}
桌子(数据协调):
public class Desk {
/*
作用:控制生产者和消费者的执行
*/
// 是否有面条 0:没有面条 1:有面条
public static int foodFlag = 0;
// 总个数,表示消费者执行的总次数
public static int count = 10;
// 锁对象
public static Object lock = new Object();
}
阻塞队列方式实现
阻塞队列体现结构
另外两个实现类是
- ArrayBlockingQueue底层是数组,有界
- LinkedBlockingQueue 底层是链表,无界。但不是真正的无界,最大为int的最大值。
我们可以通过使用ArrayBlockingQueue和LinkedBlockingQueue对象来实现阻塞队列的线程调用方式。在多个线程之间需要共享一个阻塞队列,Java帮我们实现了同步的问题。
生产者:
import java.util.concurrent.ArrayBlockingQueue;
public class Cook extends Thread{
// 阻塞队列变量,共享
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue){
this.queue = queue;
}
@Override
public void run() {
while (true){
// 不断的做出面条,放到阻塞队列中
// 注意这里不需要使用synchronized语句块或锁,
// 因为ArrayBlockingQueue内部确保了同步
try {
queue.put("面条");
System.out.println("厨师做了一碗面条!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
消费者:
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread{
// 阻塞队列变量,共享
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue){
this.queue = queue;
}
@Override
public void run() {
while (true){
// 不断的从阻塞队列中获取面条
// 底层也是加锁的,因此不需要再加synchronized
try {
String food = queue.take();
System.out.println(food);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
使用方法:
import java.util.concurrent.ArrayBlockingQueue;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
细节:
生产者和消费者必须使用同一个阻塞队列
*/
// 1.创建阻塞队列对象
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
// 2.创建线程对象,并把阻塞队列传递进去
Cook c = new Cook(queue);
Foodie f = new Foodie(queue);
// 3.开启线程
c.start();
f.start();
}
}
线程的状态
在前面章节中已经谈到线程的生命周期,这里需要再补充线程的状态。可以使用ThreadState类定义线程状态。
线程的七大状态:
注意JVM中没有定义运行状态,因为线程一旦运行起来即交由操作系统处理。因此在Java中,有以下几个状态: