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

起因

近日在复习线程相关的知识,在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线程在执行结束的时候也会调用,这也就解释了上面的现象了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 7,208评论 1 15
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 8,114评论 1 18
  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 12,081评论 3 87
  • 我一直觉得自己是活在记忆中的人,不是活在记忆里哀叹今天的种种,而是让记忆中的自己,无时无刻的鞭挞自己!我怕时光消耗...
    雪落重阳阅读 3,269评论 11 12
  • 已经好长时间没有关注群里的信息了,也不爬楼了,就好像自己脱离了这个圈子,彻底回到了沉睡的灵魂中,其实是在逃避,逃避...
    郭腾达阅读 4,028评论 0 0

友情链接更多精彩内容