欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java多線程面試題之交替輸出問題的實現(xiàn)

 更新時間:2022年01月24日 10:55:39   作者:小成同學_  
本文主要介紹了Java多線程面試題之交替輸出問題的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下

交替輸出問題

image-20211015145725097

一定要保證交替輸出,這就涉及到兩個線程的同步問題。

有人可能會想到,用睡眠時間差來實現(xiàn),但是只要是多線程里面,線程同步玩sleep()函數(shù)的,99.99%都是錯的。

這道題其實有100多種解法。

最簡單的解法

是這個問題的最優(yōu)解,但其實不是面試官想聽到的答案

關鍵函數(shù)

Locksupport.park():阻塞當前線程
Locksupport.unpark(""):喚醒某個線程

LockSupport

package com.mashibing.juc.c_026_00_interview.A1B2C3
    
import java.util.concurrent.locks.LockSupport;

public class T02_00_LockSupport {
    
    static Thread t1 = null, t2 = null;
    
    public static void main(String[] args) throws Exception {
        char[] aI = "1234567".toCharArray();
        char[] aC = "ABCDEFG".toCharArray();
    
        t1 = new Thread(() -> {

                for (char c : aI) {
                    System.out.print(c);
                    LockSupport.unpark(t2); // 叫醒t2
                    LockSupport.park(); // t1阻塞 當前線程阻塞
                }

            }, "t1");

            t2 = new Thread(() -> {

                for (char c : aC) {
                    LockSupport.park(); // t2掛起
                    System.out.print(c);
                    LockSupport.unpark(t1); // 叫醒t1
                }

            }, "t2");

            t1.start();
            t2.start();
    }
}

在這里插入圖片描述

執(zhí)行程序:

image-20211227162406095

是我們想要的結果。

面試官想聽到的解法

synchronized wait notify

package com.mashibing.juc.c_026_00_interview.A1B2C3

public class T06_00_sync_wait_notify {
    
    public static void main(String[] args) {

        final Object o = new Object();

        char[] aI = "1234567".toCharArray();
        char[] aC = "ABCDEFG".toCharArray();

        new Thread(() -> {
            // 首先創(chuàng)建一把鎖
            synchronized (o) {
                for (char c : aI) {
                    System.out.print(c);
                    try {
                        o.notify(); // 叫醒等待隊列里面的一個線程,對本程序來說就是另一個線程
                        o.wait(); // 讓出鎖
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                o.notify(); // 必須,否則無法停止程序
            }
        }, "t1").start();

        new Thread(() -> {
            synchronized (o) {
                for (char c : aC) {
                    System.out.print(c);
                    try {
                        o.notify();
                        o.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                o.notify();
            }
        }, "t2").start();
    }
}

可能有人會想,代碼中的notify()wait()順序是不是沒什么區(qū)別呢?那你就大錯特錯了,說明你不明白notify()wait()是怎么執(zhí)行的。

這道題其實是華為面試的填空題,讓你填notify()wait()。

image-20211227192836696

如果我們先執(zhí)行wait(),會先讓自己直接進入等待隊列,自己和另一個線程都在等待隊列中等待,兩個線程大??瞪小??,在那傻等,誰也叫不醒對方,也就是根本執(zhí)行不了notify()

我們發(fā)現(xiàn),在程序的后面還有一個notify(),而且還是必須有的,為什么是必須呢?我們將它注釋掉,輸出一下看看

image-20211227192149942

其實這是一個小坑。

雖然程序可以正常輸出,但是程序沒有結束;我們可以根據(jù)動圖發(fā)現(xiàn),最后一定是有一個線程是處在wait()狀態(tài)的,沒有人叫醒它,它就會永遠處在等待狀態(tài)中,從而程序無法結束,為了避免出現(xiàn)這種情況,我們要在后面加上一個notify()。

但是還有一個大坑!??!

玩過線程的應該早就發(fā)現(xiàn)了這個問題,如果第二個線程先搶到了,那么輸出的就是A1B2C3了,怎么保證第一個永遠先輸出的是數(shù)字?

我們可以使用CountDownLatch這個類,它是JUC新的同步工具,這個類可以想象成一個門栓,當我們有線程執(zhí)行到門這里,它會等待門栓把門打開,線程才會執(zhí)行;如果t2搶先一步,那么它會執(zhí)行await()方法,因為有門栓的存在,它只能在門外等待,所以t1線程會直接執(zhí)行,執(zhí)行到countDown()方法,使創(chuàng)建的CountDownLatch(1)參數(shù)置為0,即釋放門栓,所以永遠都是t1線程執(zhí)行完,t2線程才會執(zhí)行。

完整代碼

package com.mashibing.juc.c_026_00_interview.A1B2C3

import java.util.concurrent.CountDownLatch;

public class T07_00_sync_wait_notify {

    private static CountDownLatch latch = new CountDownLatch(1); // 設置門栓的參數(shù)為1,即只有一個門栓

    public static void main(String[] args) {

        final Object o = new Object();

        char[] aI = "1234567".toCharArray();
        char[] aC = "ABCDEFG".toCharArray();

        new Thread(() -> {
            synchronized (o) {
                for (char c : aI) {
                    System.out.print(c);
                    latch.countDown(); // 門栓的數(shù)值-1,即打開門
                    try {
                        o.notify();
                        o.wait(); 
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                o.notify(); 
            }
        }, "t1").start();
        
        new Thread(() -> {
            try {
                latch.await(); // 想哪個線程后執(zhí)行,await()就放在哪個線程里
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o) {
                for (char c : aC) {
                    System.out.print(c);
                    try {
                        o.notify();
                        o.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                o.notify();
            }
        }, "t2").start();
    }
}

這樣就解決了我們的擔憂。

更靈活,更精細的解法

JDK提供了很多新的同步工具,在JUC包下,其中有一個專門替代synchronized的鎖:Lock。

Lock ReentrantLock await signal

package com.mashibing.juc.c_026_00_interview.A1B2C3
    
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class T08_00_lock_condition {
    
        public static void main(String[] args) {

        char[] aI = "1234567".toCharArray();
        char[] aC = "ABCDEFG".toCharArray();

		Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
            
            lock.lock();
            
            try {
                for (char c : aI) {
                    System.out.print(c);
                    condition.signal();  // notify()
                    condition.await(); // wait()
                }
                condition.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "t1").start();

        new Thread(() -> {
            lock.lock(); // synchronized
            try {
                for (char c : aC) {
                    System.out.print(c);
                    condition.signal(); // o.notify
                    condition.await(); // o.wait
                }
                condition.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "t2").start();
    }
}

代碼表面看起來,創(chuàng)建鎖,調用方法跟synchronized沒有區(qū)別,但是關鍵點在于Condition這個類,大家應該知道生產者消費者這個概念,生產者生產饅頭,生產滿了進入等待隊列,消費者吃饅頭,吃光了同樣進入等待隊列,如果我們使用傳統(tǒng)的synchronized,當生產者生產滿時,需要從等待隊列中叫醒消費者,但調用notify方法時,我們能保證一定叫醒的是消費者嗎?不能,這件事是無法做到的,那該怎么保證叫醒的一定是消費者呢?

有兩種解決方案:

① 如果籃子已經滿了,生產者會去等待隊列中叫醒一個線程,但如果叫醒的線程還是一個生產者,那么新的生產者起來之后一定要先檢查一下籃子是否滿了,不能上來就生產,如果是滿的,那接著去叫醒下一個線程,這樣依次重復,我們一定會有一次叫醒的是消費者。

notifyAll()方法:將等待隊列中的生產者和消費者全喚醒,消費者發(fā)現(xiàn)籃子是滿的,就去消費,生產者發(fā)現(xiàn)籃子是滿的,就繼續(xù)回到等待隊列。

但不管是這兩個哪種解決方案,我們喚醒的線程都是不精確的,全都存在著浪費。

這就是synchronized做同步的問題。

image-20211230191645131

Lock本身就可以解決這個問題,靠的就是ConditionCondition可以做到精確喚醒。

Condition是條件的意思,但我們可以把它當做隊列來看待。

一個condition就是一個等待隊列。

標準代碼

package com.mashibing.juc.c_026_00_interview.A1B2C3

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class T08_00_lock_condition {

    public static void main(String[] args) {

        char[] aI = "1234567".toCharArray();
        char[] aC = "ABCDEFG".toCharArray();

        Lock lock = new ReentrantLock();
        Condition conditionT1 = lock.newCondition(); // 隊列1
        Condition conditionT2 = lock.newCondition(); // 隊列2

        CountDownLatch latch = new CountDownLatch(1);
        new Thread(() -> {

            lock.lock(); // synchronized

            try {
                for (char c : aI) {
                    System.out.print(c);
                    latch.countDown();
                    conditionT2.signal();  // o.notify()
                    conditionT1.await(); // o.wait()
                }
                conditionT2.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "t1").start();

        new Thread(() -> {

            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            lock.lock(); // synchronized

            try {
                for (char c : aC) {
                    System.out.print(c);
                    conditionT1.signal(); // o.notify
                    conditionT2.await(); // o.wait
                }
                conditionT1.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "t2").start();
    }
}

image-20211230194107871

第一個線程t1先上來持有鎖,持有鎖之后叫醒第二隊列的內容,然后自己進入第一隊列等待,同理,t2線程叫醒第一隊列的內容,自己進入第二隊列等待,這樣就可以做到精確喚醒。

到此這篇關于Java多線程面試題之交替輸出問題的實現(xiàn)的文章就介紹到這了,更多相關Java 交替輸出內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論