面試題:線程A打印1-10數(shù)字,打印到第5個(gè)數(shù)字時(shí),通知線程B

面試題:線程A打印1-10數(shù)字,打印到第5個(gè)數(shù)字時(shí),通知線程B

此題考查的是線程間的通信方式。

  • 可以利用park/unpark實(shí)現(xiàn)
  • 可以利用volatile關(guān)鍵字實(shí)現(xiàn)
  • 可以利用synchronized結(jié)合wait notify實(shí)現(xiàn)
  • 可以利用JUC中的CountDownLatch實(shí)現(xiàn)
  • 可以利用Condition中的await signal 實(shí)現(xiàn)

代碼示例

利用Park/Unpak實(shí)現(xiàn)線程通信

private void notifyThreadWithParkUnpark(){

        Thread thb  = new Thread("線程B"){
            @Override
            public void run() {
                LockSupport.park();
                System.out.println(Thread.currentThread().getName()+"啟動(dòng)了");
            }
        };
        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可以看做一個(gè)令牌,park就是等待令牌,unpark就是頒發(fā)一個(gè)令牌,另外需要注意的是park與unpark的調(diào)用次數(shù)不用一一對(duì)應(yīng),而且假如在同步代碼塊中調(diào)用park方法,線程會(huì)進(jìn)入阻塞狀態(tài),但是不會(huì)釋放已經(jīng)占用的鎖。

本例使用park使線程B進(jìn)入阻塞等待狀態(tài),在線程A調(diào)用unpark并傳入線程B的名稱使線程B可以繼續(xù)運(yùn)行。

使用Volatile關(guān)鍵字實(shí)現(xiàn)線程通信

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修飾的變量,會(huì)強(qiáng)制從主內(nèi)存中讀取變量的值。java內(nèi)存模型中關(guān)于volatile也是有說明的,volatile只能保證可見性,但不能保證原子性。

本例通過在volatile來修飾一個(gè)標(biāo)志位,線程C修改了該標(biāo)志位,然后線程D就可以“看到”標(biāo)志位的修改,從而實(shí)現(xiàn)互相通信。

使用Synchronized 集合wait notify實(shí)現(xiàn)線程間通信

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()+"啟動(dòng)了");
                    }
                }
            }
        };
        thf.start();
        try {
            Thread.sleep(500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        the.start();

    }

synchronized修飾同步代碼塊,而wait notify notify必須是在synchronized修飾代碼塊中使用,否則會(huì)拋出監(jiān)視器異常。

本實(shí)例定義一個(gè)對(duì)象鎖,而線程F首先獲取到互斥鎖,在執(zhí)行wait()方法時(shí),釋放已經(jīng)持有的互斥鎖,進(jìn)入等待隊(duì)列。而線程E執(zhí)行獲取到互斥鎖開始執(zhí)行,當(dāng)1==5時(shí),調(diào)用notify方法,就會(huì)通知lock的等待隊(duì)列,然后線程E會(huì)繼續(xù)執(zhí)行,由于線程F此時(shí)還是獲取不到互斥鎖(因?yàn)楸痪€程E占用),所以會(huì)在線程E執(zhí)行完畢后,才能獲取到執(zhí)行權(quán)。

利用CountDonwLatch實(shí)現(xiàn)線程間通信

//      倒計(jì)時(shí)器
    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()+"啟動(dòng)了");
            }
        };

        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倒計(jì)時(shí)器,利用了倒計(jì)時(shí)器的阻塞特性來實(shí)現(xiàn)等待。具體就是聲明一個(gè)計(jì)數(shù)器為1的倒計(jì)時(shí)器,線程G調(diào)用await()方法進(jìn)入等待,直到計(jì)數(shù)器為0的時(shí)候才能夠進(jìn)入執(zhí)行,而線程H在i==5會(huì)將計(jì)數(shù)器減一,使其為0,此時(shí)線程G就會(huì)繼續(xù)執(zhí)行了。

利用Condition中的await和signal來實(shí)現(xiàn)

//      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()+"啟動(dòng)了");
                        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();
    }

本示例是結(jié)合ReentrantLock和Condition來進(jìn)行控制線程間的執(zhí)行順序,Condition的await()和signal(),他們的語義和wait notify是一樣的。區(qū)別是在synchronized代碼塊里調(diào)用wait notify。通過示例可以看到這中方法實(shí)現(xiàn)會(huì)不斷的加鎖與解鎖,所以看起來稍微復(fù)雜些。

總結(jié)

通過以上代碼看到通過volatile的方式是最簡(jiǎn)潔方便,用park與unpark方式是比較靈活,不用加鎖或解鎖,剩下的synchronized與Conditon都是用了鎖,而CountDownLatch則是利用了計(jì)數(shù)器。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 相關(guān)概念 面向?qū)ο蟮娜齻€(gè)特征 封裝,繼承,多態(tài).這個(gè)應(yīng)該是人人皆知.有時(shí)候也會(huì)加上抽象. 多態(tài)的好處 允許不同類對(duì)...
    東經(jīng)315度閱讀 2,213評(píng)論 0 8
  • 下面最近發(fā)的一些并發(fā)編程的文章匯總,通過閱讀這些文章大家再看大廠面試中的并發(fā)編程問題就沒有那么頭疼了。今天給大家總...
    架構(gòu)師springboot閱讀 807評(píng)論 0 3
  • 本文出自 Eddy Wiki ,轉(zhuǎn)載請(qǐng)注明出處:http://eddy.wiki/interview-java.h...
    eddy_wiki閱讀 2,306評(píng)論 0 14
  • 沒下雪就感覺冬天還沒真正到來,盡管室內(nèi)已經(jīng)送了大暖。 傍晚風(fēng)很大,跟兒子一起去社區(qū)轉(zhuǎn)轉(zhuǎn),不經(jīng)意間發(fā)現(xiàn)...
    不系之舟a閱讀 684評(píng)論 0 3
  • “哪怕因此粉身碎骨,灰飛煙滅,也是我的宿命?!边@是蕭清羽的愛情宣言,為何他如此執(zhí)著?因?yàn)椴汕嗍悄莻€(gè)輕卷衣袂,素手弄...
    Sakura閱讀 304評(píng)論 0 1

友情鏈接更多精彩內(nèi)容