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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容

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