当前位置: 首页 > news >正文

Java学习day27:join方法、生产者消费者模式(知识点详解)

往期回顾

Java学习day26:和线程相关的Object类的方法、等待线程和唤醒线程(知识点详解)-CSDN博客Java学习day25:守护线程、死锁、线程生命周期(知识点详解)-CSDN博客

Java学习day24:线程的同步和锁(例题+知识点详解)-CSDN博客

 Java学习day27:join方法、生产者消费者模式

一、join方法【扩展】

1.常规理解

void join() 等待这个线程死亡。

 常规的理解:
这个方法作用是将当前线程挂起,不执行了,等到其他线程执行完毕后再接着执行被挂起的线程

比如: 

A->join() 挂起A线程,执行B C线程,执行完BC线程后再来执行A线程
B

 示例:

class MyThread1 implements Runnable {@Overridepublic void run() {//join方法的作用是阻塞,即等待线程结束,才继续执行。//如果使用了Thread.currentThread().join()之后//一直阻塞,无法终止
//        Thread thread = Thread.currentThread();
//        try {
//            thread.join();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ":"+i);}}
}
class MyThread2 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ":"+ i);}}
}class MyThread3 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ":"+ i);}}
}public class Demo2 {public static void main(String[] args) throws InterruptedException {//main是主线程!!!new Thread(new MyThread1(), "MyThread1").start();new Thread(new MyThread2(), "MyThread2").start();new Thread(new MyThread3(), "MyThread3").start();}
}

那么按照这个逻辑,线程1执行了join()方法,线程2和线程3没有执行,当启动了三个线程后,应该是线程2和线程3先执行,最后再执线程1。

但是,实际上的情况却是,只执行了线程2和线程3,线程1没有执行,为什么呢? 

2.实际join()方法执行

join在英语中是“加入”的意思,所以说,join()方法要做的事就是,当有新的线程加入时,主线程会进入等待状态,一直到调用join()方法的线程执行结束为止。

因为join()方法底层是就是通过wait()方法实现的。
实际上join()实现的是让"主线程"等待(WAITING状态),一直等到其他线程不再活动为止,然后"主线程"再执行。也就是说,通过join()方法,我们可以控制线程的执行顺序,执行了join()方法的线程会让其主线程进入等待状态,等到子线程都执行结束了,再让主线程执行。

面试题:
如何先让所有的子线程执行,最后再执行主线程,咋解决?     join!!!

这里要注意一点,我们一般来说都是默认main为主线程,但是这个主线程加了引号,原因很简单,这里是一个相对的概念,可以是main主线程,也可以是其他线程

示例

①以main为主线程

class MyThread1 implements Runnable {@Overridepublic void run() {//join方法的作用是阻塞,即等待线程结束,才继续执行。//如果使用了Thread.currentThread().join()之后//一直阻塞,无法终止
//        Thread thread = Thread.currentThread();
//        try {
//            thread.join();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ":"+i);}}
}
class MyThread2 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ":"+ i);}}
}public class Demo2 {public static void main(String[] args) throws InterruptedException {//main是主线程!!!Thread thread1 = new Thread(new MyThread1(), "MyThread1");thread1.start();thread1.join();Thread thread2 = new Thread(new MyThread2(), "MyThread2");thread2.start();thread2.join();for (int i = 0; i < 100; i++) {System.out.println("主线程:" + i);}}
}

join()方法的作用是让"主线程"等待(WAITING状态),一直等到其他线程不再活动为止,然后"主线程"再执行,所以这次打印的结果都是主线程在最下面!!!加join的目的是可以让主线程和子线程可控,如果不加join的话,主线程和子线程随机交叉打印!!!

面试题:先让所有的子线程执行,最后再执行主线程,咋出来?   还是join!!! 

②不以main为主线程

class Father implements Runnable {@Overridepublic void run() {//son是一个线程  Father是一个线程//但是Father 线程的run方法里面调用了Son线程//在main主函数中,只需要启动Father线程,在Father线程//又执行了Son线程,所以可堪看成Father 是Son的父线程(主线程)Son son = new Son();Thread thread = new Thread(son);thread.start();try {//加了join之后,thread 是son线程,son线程对应的主线程(Father)//会等待,等待子线程执行完以后才执行的thread.join();} catch (InterruptedException e) {e.printStackTrace();}for (int i = 0; i < 1000; i++) {System.out.println("Father线程:" + i);}}
}
class Son implements Runnable {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("son线程:" + i);}}
}
public class Demo3 {public static void main(String[] args) {Father father = new Father();Thread thread =  new Thread(father);thread.start();}
}

这里就是,没有以main为主线程,由于在执行father线程的时候会执行son线程,也就是说,son线程的父线程是father线程,所以是先执行son线程,后执行father线程。

如果说,son线程不执行join()方法,那么son线程和father线程是会交叉执行的,大家可以自行检验。

二、生产者消费者模式【重点难点】 

我们在学习计算机操作系统的时候,也会讲生产者消费者模式,这个部分在Javase中可以说是最难的知识点,需要用到之前讲的wait和notify方法!只是说业务逻辑更加的复杂了。

1.生产者消费者案例

生活中例子:

卖家:BYD汽车厂商
买家: 在座的大家
隔壁老王想买一辆 比亚迪汉   , 告知汽车厂商我要买车。隔壁老王会进入到等待状态
等到比亚迪厂家造完车以后,再通知老王来提车。如果比亚迪厂家有现车,老王就直接提车。
如果产品需要生产的话,消费者进入到阻塞状态 wait
如果产品不需要生产的话,消费者直接购买

示例:

class Goods {private String name;//商品名字private double price;//商品价格private boolean isProduct;////isProduct是否有这个商品, true  没有这个产品需要生产//false  有这个产品,不需要生产//有参构造public Goods(String name, double price, boolean isProduct) {this.name = name;this.price = price;this.isProduct = isProduct;}public String getName() {return name;}public void setName(String name) {this.name = name;}public double getPrice() {return price;}public void setPrice(double price) {this.price = price;}public boolean isProduct() {return isProduct;}public void setProduct(boolean product) {isProduct = product;}@Overridepublic String toString() {return "Goods{" +"name='" + name + '\'' +", price=" + price +", isProduct=" + isProduct +'}';}
}
//接下来搞两个线程?一个消费者线程  一个是生产者线程
class Customer implements Runnable {//为啥要定义这个goods变量? 因为两个线程要共享同一个资源!!!private Goods goods;public Customer(Goods goods) {this.goods = goods;}@Overridepublic void run() {//写业务逻辑,业务比较麻烦while (true) {//一直消费synchronized (goods) {//goods.isProduct  true 需要生产,没有商品  false 不需要生产if (!goods.isProduct()) {//不需要生产的,消费者直接购买System.out.println("消费者购买了:" + goods.getName() + ",价格为:" + goods.getPrice());//购买完以后 商品没有了 将isProduct这个变量修改为truegoods.setProduct(true);//大家有没有想过这个问题?消费者在购买的时候,生产者等待//唤醒生产者去生产车了goods.notify();//可以先防一下,等会再看!!!} else {//需要生产的!!!,没商品(咋办)//消费者进入到阻塞状态!!!try {goods.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}
}
class Producter implements  Runnable {//为啥要定义这个goods变量?private Goods goods;public Producter(Goods goods) {this.goods = goods;}@Overridepublic void run() {int count = 0;//生产者,业务逻辑比较麻烦while (true) {//你一直消费,我一直生产synchronized (goods) {if (goods.isProduct()) {//true  需要生产的!!//造车,就是赋值 对goods对象进行赋值//奇数造一种车, 偶数造另外一种车if (count % 2 == 0) {//偶数goods.setName("奥迪A8");goods.setPrice(78.9);} else {//奇数goods.setName("五菱大光");goods.setPrice(12.9);}//生产一辆车,一定要记得有车了//标记就改为 false  就证明不需要再生产了goods.setProduct(false);System.out.println("生产者生产了:" + goods.getName() + ",价格为:" + goods.getPrice());count++;//生产完以后,唤醒消费者。让消费者去提车goods.notify();} else {//不需要生产//不需要生产 有货,生产着歇着,阻塞try {goods.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}
}
public class Demo1 {public static void main(String[] args) {Goods goods = new Goods("东风41", 67.8, false);Producter producter = new Producter(goods);new Thread(producter).start();Customer customer = new Customer(goods);new Thread(customer).start();}
}

部分执行结果:

/**
         * 谁先抢到线程?消费者?还是生产者?
         * //如果是消费者抢到执行权,不用说!!!直接打印消费者购买了东风
         * //如果生产者抢到执行权,生产者wait,那就意味着必须去执行消费者线程
         * 消费者购买了:东风41,价格为:67.8
         * //此时isProduct是true 需要时生产
         * 还是消费者和生产者抢这个执行权
         * //假如生产者抢到了  就会打印生产者生产了啥。
         * //假如消费者抢到了执行权,消费者进入倒阻塞状态!!!
         * //消费者进入倒阻塞,那么肯定生产者得执行了
         * 生产者生产了:奥迪A8,价格为:78.9
         *
         * 还是两个线程抢这个执行权
         * 消费者购买了:奥迪A8,价格为:78.9
         * 生产者生产了:五菱大光,价格为:12.9
         * 消费者购买了:五菱大光,价格为:12.9
         * 生产者生产了:奥迪A8,价格为:78.9
         * 消费者购买了:奥迪A8,价格为:78.9
         * 生产者生产了:五菱大光,价格为:12.9
         * 消费者购买了:五菱大光,价格为:12.9
         * 生产者生产了:奥迪A8,价格为:78.9
         * 消费者购买了:奥迪A8,价格为:78.9
         */

这个代码示例里有很多我们需要注意的,业务逻辑实现非常复杂,我们一个一个来说。

1.消费者和生产者两个线程,为什么还要单独写一个Goods类:
 
还记得之前讲等待线程和唤醒线程的时候讲的Message类
因为两个线程要通信。这个Goods就是通信的桥梁!!!
goods.wait()  消费者等待
goods.notoify()  生产者唤醒消费者
2.goods在消费者和生产者两个线程里都分别使用了wait()和notify()方法要知道消费者线程里执行的notify()方法不是用来唤醒其执行的wait()方法的而是唤醒生产者线程里执行了wait()方法的,也就是说,两个是交叉执行的。
3.最后的代码执行结果是需要根据谁先抢占到cpu执行权,才能决定是生产者先执行还是消费者先执行。

 以上,就是今天的所有知识点了。生产者消费者这个案例的业务处理有点复杂,大家最开始看着懵逼是很正常的,这个东西需要沉淀!!!大家一定要紧紧围绕者线程抢占式执行的,然后分析结果!!!大家要自己多花点时间,静下心看代码,写代码,多理解,多运用,重点是多去运用。

加油吧,预祝大家变得更强!

相关文章:

  • 通过 React 来构建界面
  • Android PMS——系统应用位置解析(四)
  • redis redisson报错 Unsupported protocol问题原因和解决方案
  • 微信小程序(二十八)网络请求数据进行列表渲染
  • 数据库管理-第143期 Oracle DB 19c需要调整的基本参数V2(20240202)
  • JSON字符串作为入参时,转换为具体对象
  • Leetcode—2881. 创建新列【简单】
  • VBA数据库解决方案第八讲:SQL语句及打开记录集
  • 图片热区功能
  • Flutter的安装与环境配置
  • 机器学习 | 如何利用集成学习提高机器学习的性能?
  • 030 可变参数
  • [SWPUCTF 2021 新生赛]ez_unserialize
  • Oracle和Mysql数据库
  • flutter如何实现省市区选择器
  • python3.6+scrapy+mysql 爬虫实战
  • Create React App 使用
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • Java到底能干嘛?
  • PHP变量
  • React 快速上手 - 06 容器组件、展示组件、操作组件
  • ReactNative开发常用的三方模块
  • use Google search engine
  • 创建一个Struts2项目maven 方式
  • 浅谈web中前端模板引擎的使用
  • 详解NodeJs流之一
  • ​总结MySQL 的一些知识点:MySQL 选择数据库​
  • #预处理和函数的对比以及条件编译
  • ( )的作用是将计算机中的信息传送给用户,计算机应用基础 吉大15春学期《计算机应用基础》在线作业二及答案...
  • (2)Java 简介
  • (多级缓存)多级缓存
  • (附源码)计算机毕业设计ssm基于B_S的汽车售后服务管理系统
  • (含react-draggable库以及相关BUG如何解决)固定在左上方某盒子内(如按钮)添加可拖动功能,使用react hook语法实现
  • (黑客游戏)HackTheGame1.21 过关攻略
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (七)微服务分布式云架构spring cloud - common-service 项目构建过程
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • .bat文件调用java类的main方法
  • .NET CF命令行调试器MDbg入门(二) 设备模拟器
  • .Net Core webapi RestFul 统一接口数据返回格式
  • .Net Core缓存组件(MemoryCache)源码解析
  • .NET Micro Framework 4.2 beta 源码探析
  • .net mvc部分视图
  • .NET 事件模型教程(二)
  • .Net 知识杂记
  • .NetCore部署微服务(二)
  • .NET连接数据库方式
  • :not(:first-child)和:not(:last-child)的用法
  • [ai笔记4] 将AI工具场景化,应用于生活和工作
  • [ai笔记9] openAI Sora技术文档引用文献汇总
  • [bzoj1324]Exca王者之剑_最小割
  • [C#]C# OpenVINO部署yolov8图像分类模型
  • [C#]winform使用引导APSF和梯度自适应卷积增强夜间雾图像的可见性算法实现夜间雾霾图像的可见度增强
  • [C#]无法获取源 https://api.nuge t.org/v3-index存储签名信息解决方法
  • [c]统计数字