zoukankan      html  css  js  c++  java
  • 颠覆我的Thread.join()

    学而时习之,不亦说乎!

                                 --《论语》

      为什么说是颠覆?

      1)任何对象都可以作为锁对象,锁对象的行为都是一样的吗?之前我一直认为锁对象的方法都是定义在Object类中,而所有类都是Object的子类,这些方法又都是native方法,那么用哪个对象作为锁对象又有什么区别呢?

      2)一个线程对象a在run()方法内部调用线程对象b的join()方法,那么是将b线程加入,等到b线程执行完毕再执行a线程?那么如果还有一个正在执行的c线程呢,线程c也会等待b执行完吗?

    代码1:

    package com.zby;
    
    public class Application1 {
    
        public static void main(String[] args) {
            Thread prepare = new Thread(new Runnable() {
                public void run() {
                    for (int i = 0; i < 5; i++) {
                        System.out.println("Hello,World!-----" + i);
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            prepare.start();
            System.out.println("Hello,ZBY!");
        }
    
    }

    控制台输出:

    Hello,ZBY!
    Hello,World!-----0
    Hello,World!-----1
    Hello,World!-----2
    Hello,World!-----3
    Hello,World!-----4

    结果不难分析,主线程直接执行,而prepare线程虽然启动了,但是执行没那么快,所以后执行了。但是我的prepare是进行准备工作的,我想让prepare线程执行完毕后再执行主线程。

    代码2:

    package com.zby;
    
    public class Application2 {
    
        public static void main(String[] args) {
            Thread prepare = new Thread(new Runnable() {
                public void run() {
                    for (int i = 0; i < 5; i++) {
                        System.out.println("Hello,World!-----" + i);
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            prepare.start();
            try {
                prepare.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Hello,ZBY!");
        }
    }

    控制台输出:

    Hello,World!-----0
    Hello,World!-----1
    Hello,World!-----2
    Hello,World!-----3
    Hello,World!-----4
    Hello,ZBY!

    很小儿科,加了一个一行代码:prepare.join();要是之前我会理解成把prepare加入到主线程先执行,执行完才能执行其它线程。然而,非也。

    Thread.join()源代码:

        public final void join() throws InterruptedException {
            join(0);
        }
        public final synchronized void join(long millis)
        throws InterruptedException {
            long base = System.currentTimeMillis();
            long now = 0;
    
            if (millis < 0) {
                throw new IllegalArgumentException("timeout value is negative");
            }
    
            if (millis == 0) {
                while (isAlive()) {
                    wait(0);
                }
            } else {
                while (isAlive()) {
                    long delay = millis - now;
                    if (delay <= 0) {
                        break;
                    }
                    wait(delay);
                    now = System.currentTimeMillis() - base;
                }
            }
        }

    这儿可以看出来,我们调用prepare.join()的时候发生了什么:把prepare作为锁对象,调用锁对象的wait(0)方法,阻塞当前线程!也就是说,并不是把需要执行的线程加入进来让他先执行,而是阻塞当前的线程!那么,如果还有第三个线程也在执行,那么prepare线程是不会一直执行,而是跟第三个线程抢CPU执行权。总结起来就是,其实就是阻塞了当前的线程,对于其他线程都是没有影响的。感兴趣可以加入第三个线程自己测试一下。

    等价于代码2的代码3:

    package com.zby;
    
    public class Application3 {
    
        public static void main(String[] args) {
            Thread prepare = new Thread(new Runnable() {
                public void run() {
                    for (int i = 0; i < 5; i++) {
                        System.out.println("Hello,World!-----" + i);
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            prepare.start();
            synchronized(prepare){
                try {
                    prepare.wait(0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Hello,ZBY!");
        }
    }

    控制台输出:

    Hello,World!-----0
    Hello,World!-----1
    Hello,World!-----2
    Hello,World!-----3
    Hello,World!-----4
    Hello,ZBY!

    这儿就解决了第二个问题,join()方法其实不是把调用的线程加入进来优先执行,而是阻塞当前线程!

    看完代码3就有疑问了,prepare.wait(0);自然没错,阻塞住了主线程。但是并没有任何地方调用notify或者notifyAll方法,线程不是应该一直阻塞么,怎么会在prepare执行完后继续执行主线程代码了?

    这就是第一个问题了,普通对象当然是wait后必须等待notify唤醒才能继续执行,但是Thread对象呢?具体的我也不知道,但是从这儿可以推论出,thread对象在执行完毕后,自动唤醒了!那么到底是notify还是notifyAll呢?那么,多启动一个线程,并使用prepare对象作为锁对象,调用wait方法。

    代码4:

    package com.zby;
    
    public class Application3 {
    
        public static void main(String[] args) {
            final Thread prepare = new Thread(new Runnable() {
                public void run() {
                    for (int i = 0; i < 5; i++) {
                        System.out.println("Hello,World!-----" + i);
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            prepare.start();
            Thread ready = new Thread(new Runnable() {
                public void run() {
                    synchronized (prepare) {
                        try {
                            prepare.wait(0);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    for (int i = 0; i < 10; i++) {
                        System.out.println("Hello,Earth!-----" + i);
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            ready.start();
            synchronized (prepare) {
                try {
                    prepare.wait(0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Hello,ZBY!");
        }
    }

    控制台输出:

    Hello,World!-----0
    Hello,World!-----1
    Hello,World!-----2
    Hello,World!-----3
    Hello,World!-----4
    Hello,Earth!-----0
    Hello,ZBY!
    Hello,Earth!-----1
    Hello,Earth!-----2
    Hello,Earth!-----3
    Hello,Earth!-----4
    Hello,Earth!-----5
    Hello,Earth!-----6
    Hello,Earth!-----7
    Hello,Earth!-----8
    Hello,Earth!-----9

    可以看出,在preare执行完之后,自动把ready和main线程都唤醒了!也就是说,使用Thread对象作为锁对象,在Thread执行完成之后,会唤醒使用该对象作为锁对象调用wait()休眠的线程。

    总结:

    1)使用线程的join方法,是将调用的线程对象作为锁对象,阻塞当前线程,不影响其他线程的运行。

    2)推论:Thread对象作为线程锁对象,会在Thread对象执行完后,调用Thread对象的notifyAll方法。

    那么,下面的代码会发生什么呢?

    程序不会停止,也检测不到死锁!但是这确实是一个死循环

        public static void main(String[] args) throws InterruptedException {
            Thread.currentThread().join();
        }  
  • 相关阅读:
    关于Java常见的误解
    Java程序设计概述
    是结束,更是开始!
    从零开始单排学设计模式「简单工厂设计模式」黑铁 III
    某神秘公司 RESTful、共用接口、前后端分离、接口约定的实践
    这40张图送给单身程序员,情人节请一笑而过!
    科技圈晒开工福利!2019一起定个小目标!
    IDEA一定要懂的32条快捷键
    假期结束了,我相信未来会更好!
    有一种痛,叫 “今年没有年终奖”!!!
  • 原文地址:https://www.cnblogs.com/zby9527/p/7526606.html
Copyright © 2011-2022 走看看