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
C
示例:
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执行权,才能决定是生产者先执行还是消费者先执行。 |
以上,就是今天的所有知识点了。生产者消费者这个案例的业务处理有点复杂,大家最开始看着懵逼是很正常的,这个东西需要沉淀!!!大家一定要紧紧围绕者线程抢占式执行的,然后分析结果!!!大家要自己多花点时间,静下心看代码,写代码,多理解,多运用,重点是多去运用。
加油吧,预祝大家变得更强!