面试题:线程A打印1-10数字,打印到第5个数字时,通知线程B

面试题:线程A打印1-10数字,打印到第5个数字时,通知线程B

此题考查的是线程间的通信方式。

  • 可以利用park/unpark实现
  • 可以利用volatile关键字实现
  • 可以利用synchronized结合wait notify实现
  • 可以利用JUC中的CountDownLatch实现
  • 可以利用Condition中的await signal 实现

代码示例

利用Park/Unpak实现线程通信

private void notifyThreadWithParkUnpark(){

        Thread thb  = new Thread("线程B"){
            @Override
            public void run() {
                LockSupport.park();
                System.out.println(Thread.currentThread().getName()+"启动了");
            }
        };
        Thread tha =new Thread("线程A"){
            @Override
            public void run() {
                for(int i=1;i<11;i++){
                    System.out.println(Thread.currentThread().getName()+i);
                    if(i==5){
                        LockSupport.unpark(thb);
                    }
                }
            }
        };
        thb.start();
        tha.start();
    }

park与unpark可以看做一个令牌,park就是等待令牌,unpark就是颁发一个令牌,另外需要注意的是park与unpark的调用次数不用一一对应,而且假如在同步代码块中调用park方法,线程会进入阻塞状态,但是不会释放已经占用的锁。

本例使用park使线程B进入阻塞等待状态,在线程A调用unpark并传入线程B的名称使线程B可以继续运行。

使用Volatile关键字实现线程通信

private static volatile boolean flag = false;

private void notifyThreadWithVolatile(){
        Thread thc= new Thread("线程C"){
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    if(i==5){
                        flag=true;
                        try {
                            Thread.sleep(500L);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(Thread.currentThread().getName()+i);
                }
            }
        };

        Thread thd= new Thread("线程D"){
            @Override
            public void run() {
                while (true){
                    // 防止伪唤醒 所以使用了while
                    while(flag){
                        System.out.println(Thread.currentThread().getName()+"收到通知");
                        break;
                    }
                }
            }
        };

        thd.start();
        try {
            Thread.sleep(1000L);
        } catch (Exception e) {
            e.printStackTrace();
        }
        thc.start();

    }

volatile表示的禁用CPU缓存,用volatile修饰的变量,会强制从主内存中读取变量的值。java内存模型中关于volatile也是有说明的,volatile只能保证可见性,但不能保证原子性。

本例通过在volatile来修饰一个标志位,线程C修改了该标志位,然后线程D就可以“看到”标志位的修改,从而实现互相通信。

使用Synchronized 集合wait notify实现线程间通信

private static final Object lock = new Object();

private void notifyThreadWithSynchronized(){
        Thread the = new Thread("线程E"){
            @Override
            public void run() {
                synchronized (lock){
                    for (int i = 0; i <10 ; i++) {
                        System.out.println(Thread.currentThread().getName()+i);
                        if(i==5){
                            lock.notify();
                        }
                    }
                }
            }
        };


        Thread thf = new Thread("线程F"){
            @Override
            public void run() {
                while(true){
                    synchronized (lock){
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"启动了");
                    }
                }
            }
        };
        thf.start();
        try {
            Thread.sleep(500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        the.start();

    }

synchronized修饰同步代码块,而wait notify notify必须是在synchronized修饰代码块中使用,否则会抛出监视器异常。

本实例定义一个对象锁,而线程F首先获取到互斥锁,在执行wait()方法时,释放已经持有的互斥锁,进入等待队列。而线程E执行获取到互斥锁开始执行,当1==5时,调用notify方法,就会通知lock的等待队列,然后线程E会继续执行,由于线程F此时还是获取不到互斥锁(因为被线程E占用),所以会在线程E执行完毕后,才能获取到执行权。

利用CountDonwLatch实现线程间通信

//      倒计时器
    private CountDownLatch cdl = new CountDownLatch(1);

private void notifyThreadWithCountDownLatch(){
        Thread thg = new Thread("线程G"){
            @Override
            public void run() {
                try {
                    cdl.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"启动了");
            }
        };

        thg.start();

        Thread thh = new Thread("线程H"){
            @Override
            public void run() {
                for (int i = 1; i < 11; i++) {
                    System.out.println(Thread.currentThread().getName()+i);
                    if(i==5){
                        cdl.countDown();
                    }
                }

            }
        };

        thh.start();
    }

本示例中使用了CountDownLatch倒计时器,利用了倒计时器的阻塞特性来实现等待。具体就是声明一个计数器为1的倒计时器,线程G调用await()方法进入等待,直到计数器为0的时候才能够进入执行,而线程H在i==5会将计数器减一,使其为0,此时线程G就会继续执行了。

利用Condition中的await和signal来实现

//      ReentrantLock+ condition
    private Lock rtl=new ReentrantLock();
    private Condition condition = rtl.newCondition();

private void notifyThreadWithCondition(){

        Thread thi = new Thread("线程I"){
            @Override
            public void run() {

                while (true){
                    rtl.lock();
                    try {
                        condition.await();
                        System.out.println(Thread.currentThread().getName()+"启动了");
                        break;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        rtl.unlock();
                    }
                }
            }
        };


        Thread thj = new Thread("线程J"){
            @Override
            public void run() {
                rtl.lock();
                try {
                    for (int i = 0; i < 10; i++) {
                        System.out.println(Thread.currentThread().getName()+i);
                        if(i==5){
                            condition.signal();
                        }
                    }
                } finally {
                    rtl.unlock();
                }

            }
        };

        thi.start();
        try {
            Thread.sleep(500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thj.start();
    }

本示例是结合ReentrantLock和Condition来进行控制线程间的执行顺序,Condition的await()和signal(),他们的语义和wait notify是一样的。区别是在synchronized代码块里调用wait notify。通过示例可以看到这中方法实现会不断的加锁与解锁,所以看起来稍微复杂些。

总结

通过以上代码看到通过volatile的方式是最简洁方便,用park与unpark方式是比较灵活,不用加锁或解锁,剩下的synchronized与Conditon都是用了锁,而CountDownLatch则是利用了计数器。

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

推荐阅读更多精彩内容

  • 相关概念 面向对象的三个特征 封装,继承,多态.这个应该是人人皆知.有时候也会加上抽象. 多态的好处 允许不同类对...
    东经315度阅读 1,938评论 0 8
  • 下面最近发的一些并发编程的文章汇总,通过阅读这些文章大家再看大厂面试中的并发编程问题就没有那么头疼了。今天给大家总...
    架构师springboot阅读 681评论 0 3
  • 本文出自 Eddy Wiki ,转载请注明出处:http://eddy.wiki/interview-java.h...
    eddy_wiki阅读 2,104评论 0 14
  • 没下雪就感觉冬天还没真正到来,尽管室内已经送了大暖。 傍晚风很大,跟儿子一起去社区转转,不经意间发现...
    纶纶妈阅读 476评论 0 3
  • “哪怕因此粉身碎骨,灰飞烟灭,也是我的宿命。”这是萧清羽的爱情宣言,为何他如此执着?因为采青是那个轻卷衣袂,素手弄...
    Sakura阅读 211评论 0 1