美文网首页
Java 线程协作wait()、sleep()、join()

Java 线程协作wait()、sleep()、join()

作者: 砂砾han | 来源:发表于2018-04-25 14:02 被阅读29次

    起因

    近日在复习线程相关的知识,在join()方法有些遗忘,查看网上的各种文章也多有纰漏、错误,所以做一下记录,备忘。

    从一道面试题说起:wait()和sleep()的区别

    这是一道特别常见的面试题,要理解它的内在区别还需要了解一下Java的Monitor Object设计模式,这里简单说明一下,有兴趣的可以查阅相关资料。


    Java Monitor.jpg

    Java Monitor 从两个方面来支持线程之间的同步,即:互斥执行与协作。 Java 使用对象锁 ( 使用 synchronized 获得对象锁 ) 保证工作在共享的数据集上的线程互斥执行 , 使用 notify/notifyAll/wait 方法来协同不同线程之间的工作。这些方法在 Object 类上被定义,会被所有的 Java 对象自动继承。
    如上图,线程如果获得监视锁成功,将成为该监视者对象的拥有者。在任一时刻内,监视者对象只属于一个活动线程 (Owner) 。拥有者线程可以调用 wait 方法自动释放监视锁,进入等待状态。

    所以wait()和sleep()的区别我们可以从以下几个方面来思考:
    • wait()是Object类定义的方法,sleep()是Thread类的静态方法;
    • Thread.sleep不会导致锁行为的改变,如果当前线程是拥有锁的,那么Thread.sleep不会让线程释放锁。而wait()会使当前线程让出对象锁,进入Wait Set(如上图);
    • Thread.sleep()和Object.wait()都会暂停当前的线程,对于CPU资源来说,不管是哪种方式暂停的线程,都表示它暂时不再需要CPU的执行时间。OS会将执行时间分配给其它线程。区别是,调用wait后,需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间(还有一些interrupt操作)。

    关于join()

    我们先看一个小demo:

    public class TestJoin {
    
        public static void main(String[] args) {
            System.out.println("Main start!");
            long start  = System.currentTimeMillis();
            Thread t1 = new Thread(() -> {
                System.out.println("t1 begin! " + (System.currentTimeMillis() - start));
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("t1 end! " + (System.currentTimeMillis() - start));
                }
            });
    
            Thread t2 = new Thread(() -> {
                System.out.println("t2 begin! " + (System.currentTimeMillis() - start));
                try {
                    t1.join(1000);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("t2 end! " + (System.currentTimeMillis() - start));
                }
            });
    
            t1.start();
            t2.start();
        }
    }
    

    我们join的参数为1000

    Main start!
    t1 begin! 96
    t2 begin! 96
    t1 end! 2100
    t2 end! 4098
    

    如果我们join2000呢?

    Main start!
    t1 begin! 99
    t2 begin! 99
    t1 end! 2104
    t2 end! 5105
    

    3000?

    Main start!
    t1 begin! 88
    t2 begin! 88
    t1 end! 2091
    t2 end! 5094
    

    结论

    从上面的结果来看,t1.join(timeout)让正在运行的t2阻塞了,阻塞的时长取决于timeout的大小和t1运行的时长。

    原理分析

    首先我们还是先看join的源码:

     /**
         * Waits at most {@code millis} milliseconds for this thread to
         * die. A timeout of {@code 0} means to wait forever.
         *
         * <p> This implementation uses a loop of {@code this.wait} calls
         * conditioned on {@code this.isAlive}. As a thread terminates the
         * {@code this.notifyAll} method is invoked. It is recommended that
         * applications not use {@code wait}, {@code notify}, or
         * {@code notifyAll} on {@code Thread} instances.
         *
         * @param  millis
         *         the time to wait in milliseconds
         *
         * @throws  IllegalArgumentException
         *          if the value of {@code millis} is negative
         *
         * @throws  InterruptedException
         *          if any thread has interrupted the current thread. The
         *          <i>interrupted status</i> of the current thread is
         *          cleared when this exception is thrown.
         */
        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;
                }
            }
        }
    

    至此我们知道了join的内部其实是用wait来实现的,所以当我们在t2线程里调用 t1.join() 时会造成t2的阻塞,除非timeout超时,或者其他线程调用了t1.notify/notifyAll才会终止这个wait,而t1线程在执行结束的时候也会调用,这也就解释了上面的现象了。

    相关文章

      网友评论

          本文标题:Java 线程协作wait()、sleep()、join()

          本文链接:https://www.haomeiwen.com/subject/jueelftx.html