核心思路
我们假设有生产者和消费者
用桌子上的共享数据控制线程的执行
消费者先拿到CPU的执行权 就会wait
生产者后拿到CPU的执行权 唤醒消费者
生产者等待
是因为没有消费者
生产者先抢到CPU的执行权
然后等待消费者
然后消费者没有出现 生产者又抢到了CPU的执行权
就会wait
lock锁两个关键方法
wait()方法
在Java中,wait()
方法是用于线程间通信和协作的关键方法之一。当一个线程调用某个对象的wait()
方法时,它会释放对象的锁,并进入等待状态,直到其他线程调用相同对象的notify()
或notifyAll()
方法来唤醒它。
具体来说,当一个线程调用wait()
方法时,它会暂时让出对象的锁,允许其他线程获取该对象的锁并执行。同时,调用wait()
方法的线程会进入等待队列,等待其他线程通过调用notify()
或notifyAll()
来唤醒它。
wait()
方法通常与synchronized
关键字一起使用,以确保线程在进入等待状态前能够正确释放对象的锁。这种机制可以用于实现线程之间的协作,例如等待某个条件满足后再继续执行。
需要注意的是,在调用wait()
方法前,线程必须先获得对象的监视器(即锁),否则会抛出IllegalMonitorStateException
异常。
notifyAll()方法
在Java中,notifyAll()
方法是用于多线程编程中的对象监视器方法之一。当一个线程调用某个对象的notifyAll()
方法时,它会唤醒所有正在等待这个对象监视器(即锁)的线程。
具体来说,当一个线程调用某个对象的notifyAll()
方法时,该对象上所有因调用wait()
方法而被阻塞的线程都会被唤醒,这些线程将会开始竞争对象锁。然后,只有一个线程可以获取到对象锁并继续执行,其他线程将继续等待或者重新竞争锁。
这个方法通常用于实现线程间的协作,让等待某个条件满足的线程能够及时得到通知并继续执行。需要注意的是,在使用notifyAll()
时,要确保唤醒的线程都能正确处理唤醒信号,避免出现死锁或者竞态条件等问题。
自我理解
规则是消费者只能吃10碗面条
假设我是消费者
如果是我抢到了线程 我首先先判断是否已经达到了十碗面条的量 用变量记录
然后向下执行
看桌上有没有面条
如果没有面条 抛出锁的对象 让厨师进入线程 做面条
如果有面条 进行核心处理 打印输出我已经吃了 并且进行标记处理标记此时已经没有面条了
如果我是厨师
我先抢到了线程 我首先也是判断是否达到了十碗面条的数量
然后向下执行
如果桌子上有面条 抛出锁的对象 让消费者进入线程 吃面条
如果没有面条 就做面条 标记此时已经有面条了 做完后唤醒消费者线程 此时消费者得进来吃面条
关键
静态变量 两个线程共享的
同时得设置标记 比如说桌面上有无面条
如何标记记录 核心执行逻辑
要把各个方面都考虑
例如 我作为消费者吃了面条 不仅桌面上没有面条了 而且面条计数还要减1
而且我们还要唤醒厨师线程
如何设计结构
我们首先判断的是大条件 就是有没有吃到10碗 否则直接退出
接下来是有没有面条 你等我 我等你
代码实现
public class Desk {
//共享的数据
public static int foodFlag=0;//表示桌面上是否有面条 0表示没有面条 1表示有面条
public static int count=10;//表示面条的总个数
public static Object lock=new Object();//锁
}
public class Foodie extends Thread{
@Override
public void run() {
/*
* 循环
* 同步代码块
* 判断共享数据是否到达了末尾
* 优先处理到达了末尾
* 随后处理未到达末尾的 执行核心逻辑
* */
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 {
//有面条 吃货得吃掉面条
System.out.println("吃货吃了面条,还能再吃"+(Desk.count-1)+"碗");
//唤醒厨师
Desk.lock.notifyAll();
//总数减去1
Desk.count--;
//修改桌子的状态 此时是没有面条的了
Desk.foodFlag=0;
}
}
}
}
}
}
public class Cook extends Thread{
@Override
public void run() {
/*
* 循环
* 同步代码块
* 判断共享数据是否到达了末尾
* 先写到达末尾了
* 在写没有到达末尾的 执行核心逻辑
* */
while (true){
synchronized (Desk.lock){
if(Desk.count==0)break;
else {
if(Desk.foodFlag==1){//代表桌面上有 这时应该等待
try {
Desk.lock.wait();//让出锁的对象
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
//桌面上没有食物 执行核心逻辑
System.out.println("厨师做了一碗面条");
//修改标记
Desk.foodFlag=1;
//此时吃货线程在等待 唤醒吃货线程
//唤醒正在等待该锁的所有线程 但是只能抛出一把锁的对象
Desk.lock.notifyAll();
}
}
}
}
}
}
public class Main {
public static void main(String[] args) {
//创建线程
Foodie f=new Foodie();
Cook c=new Cook();
//给线程设置名字
f.setName("吃货");
c.setName("厨师");
//一起启动
f.start();
c.start();
}
}
输出
阻塞队列方式实现
阻塞队列表示连接消费者和生产者之间的管道
可以规定管道内最多放多少面条
(图片来自黑马程序员)
生产者和消费者必须使用同一个阻塞队列
在put方法当中
底层
会用lock锁的方式把方法锁起来
先获取锁的对象 获取锁 循环判断 判断队列里的元素个数和队列长度是否相等
如果满了就会等待 没有满就会里面添加元素
最后在调用unlock 释放锁
所以我们在书写时就不用写锁
take方法底层也是有锁的
实际效果
所以这种方式会简洁
实际上就是借助了阻塞队列这个容器
而阻塞队列底层有锁结构 可以降低我们书写代码的数量
这种方式关键在于要用一个阻塞队列 是通过书写构造方法实现的
在测试类里往里面传入参数 而这个参数是测试类创建的阻塞队列
import java.util.concurrent.ArrayBlockingQueue;
public class Cook extends Thread{
ArrayBlockingQueue<String>queue;
//生成构造函数 创建对象的时候给queue赋值
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
//不断的从阻塞队列中添加面条
while (true){
try {
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread{
ArrayBlockingQueue<String> queue;
//生成构造函数 创建对象的时候给queue赋值
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true){
//不断的从阻塞队列中获取面条
try {
String food=queue.take();
System.out.println(food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import javax.crypto.spec.PSource;
import java.util.concurrent.ArrayBlockingQueue;
public class Main {
public static void main(String[] args) {
//阻塞队列的对象创建在测试类里面
//有界的阻塞队列 创建对象的时候指定上限
ArrayBlockingQueue<String>queue=new ArrayBlockingQueue<>(1);
//创建线程对象 并且把阻塞队列传递过去
Cook c=new Cook(queue);
Foodie f=new Foodie(queue);
c.start();
f.start();
}
}
最后打印输出结果
个人号推广
博客主页
Web后端开发
https://blog.csdn.net/qq_30500575/category_12624592.html?spm=1001.2014.3001.5482
Web前端开发
https://blog.csdn.net/qq_30500575/category_12642989.html?spm=1001.2014.3001.5482
数据库开发
https://blog.csdn.net/qq_30500575/category_12651993.html?spm=1001.2014.3001.5482
项目实战
https://blog.csdn.net/qq_30500575/category_12699801.html?spm=1001.2014.3001.5482
算法与数据结构
https://blog.csdn.net/qq_30500575/category_12630954.html?spm=1001.2014.3001.5482
计算机基础
https://blog.csdn.net/qq_30500575/category_12701605.html?spm=1001.2014.3001.5482
回忆录
https://blog.csdn.net/qq_30500575/category_12620276.html?spm=1001.2014.3001.5482