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 {

    @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没有执行,为什么呢? 

2.实际join()方法执行

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方法!只是说业务逻辑更加的复杂了。

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;
    }

    @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执行权,才能决定是生产者先执行还是消费者先执行。

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

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

你可能感兴趣的:(java从0到1,java,学习,开发语言,intellij-idea,java-ee)