往期回顾
Java学习day26:和线程相关的Object类的方法、等待线程和唤醒线程(知识点详解)-CSDN博客Java学习day25:守护线程、死锁、线程生命周期(知识点详解)-CSDN博客
Java学习day24:线程的同步和锁(例题+知识点详解)-CSDN博客
void join() 等待这个线程死亡。
常规的理解:
这个方法作用是将当前线程挂起,不执行了,等到其他线程执行完毕后再接着执行被挂起的线程
比如:
A->join() 挂起A线程,执行B C线程,执行完BC线程后再来执行A线程
B
C
示例:
class MyThread1 implements Runnable {
@Override
public 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 {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":"+ i);
}
}
}
class MyThread3 implements Runnable {
@Override
public 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没有执行,为什么呢?
join在英语中是“加入”的意思,所以说,join()方法要做的事就是,当有新的线程加入时,主线程会进入等待状态,一直到调用join()方法的线程执行结束为止。
因为join()方法底层是就是通过wait()方法实现的。
实际上join()实现的是让"主线程"等待(WAITING状态),一直等到其他线程不再活动为止,然后"主线程"再执行。也就是说,通过join()方法,我们可以控制线程的执行顺序,执行了join()方法的线程会让其主线程进入等待状态,等到子线程都执行结束了,再让主线程执行。
面试题:
如何先让所有的子线程执行,最后再执行主线程,咋解决? join!!!
这里要注意一点,我们一般来说都是默认main为主线程,但是这个主线程加了引号,原因很简单,这里是一个相对的概念,可以是main主线程,也可以是其他线程
示例
①以main为主线程
class MyThread1 implements Runnable {
@Override
public 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 {
@Override
public 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 {
@Override
public 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 {
@Override
public 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方法!只是说业务逻辑更加的复杂了。
生活中例子:
卖家: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;
}
@Override
public 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;
}
@Override
public void run() {
//写业务逻辑,业务比较麻烦
while (true) {//一直消费
synchronized (goods) {
//goods.isProduct true 需要生产,没有商品 false 不需要生产
if (!goods.isProduct()) {
//不需要生产的,消费者直接购买
System.out.println("消费者购买了:" + goods.getName() + ",价格为:" + goods.getPrice());
//购买完以后 商品没有了 将isProduct这个变量修改为true
goods.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;
}
@Override
public 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执行权,才能决定是生产者先执行还是消费者先执行。 |
以上,就是今天的所有知识点了。生产者消费者这个案例的业务处理有点复杂,大家最开始看着懵逼是很正常的,这个东西需要沉淀!!!大家一定要紧紧围绕者线程抢占式执行的,然后分析结果!!!大家要自己多花点时间,静下心看代码,写代码,多理解,多运用,重点是多去运用。
加油吧,预祝大家变得更强!