Java 高併發編程 一道多線程 面試題目

實現一個容器,提供兩個方法 add, size
寫倆個線程,線程1添加10個元素道容器中,線程2實現監控元素得個數,當個數到5的時候,線程2給出提示並結束。
 

  • 因爲涉及到兩個線程訪問同一個變量,所以應該是變量共享的,會使用到volatile或者synchronized,(只涉及到一個線程去修改資源,另外一個線程去訪問,所以這裏同步方法可能不大適合)
  • 需要另外一個線程去輪循容器是否已經有5個元素來,這樣會很大的浪費資源
  • wait和notify功能,兩個線程共同鎖定一個對象,通過這個對象,使用wait和notify 使倆個線程來啓東和釋放鎖,這裏需要搶到的一點 wait 會釋放鎖。
public class Container {

    volatile List lists = new ArrayList();

    public void add(Object o){
        lists.add(o);
    }

    public int size(){
        return lists.size();
    }

    public static void main(String[] args){
        Container container = new Container();

        final Object o = new Object();

        new Thread(() ->{
            synchronized (o){
                System.out.println("t2 啓動");// t2 監控元素
                if (container.size()!=5){
                    try {
                        o.wait();// 釋放
                        System.out.println("5個添加夠了 t1");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("t2 結束");
            }
        }, "t2").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()-> {
            System.out.println("t1 啓動");// t1 添加元素

            synchronized (o){
                for (int i = 0; i < 10; i++) {
                    container.add(new Object());
                    System.out.println("add: " + i);
                    if (container.size() == 5) {
                        o.notify();
                    }
                }

                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            System.out.println("t1 結束");
            }
        }, "t1").start();
    }
}

執行上面的代碼會有個問題:t2 線程並沒有在t1線程增加到第5個元素得時候啓動,雖然在上面的代碼中t2線程有去notify t1線程,但是線程並沒有打印出來我們想要的結果,而是在t1結束之後纔打印出來。

之所以出現這個問題是因爲 notify 並不會去釋放鎖,所以即使在t1線程中notify t2線程,但是t2線程並沒有重新獲得鎖(在程序剛啓動的時候t2  進入 wait 狀態,釋放了鎖)。

解決的方法是,在t1程序 notify t2 得時候需要釋放鎖,使用的方法就是 讓t1也進入wait狀態,然後t2程序獲得鎖,執行結束之後,執行結束之後會釋放鎖,再notify t1線程運行。

notify之後,t1必須釋放鎖,t2退出後,也必須notify,通知t1繼續執行
 * 整個通信過程比較繁瑣
 * 
 * 使用Latch(門閂)替代wait notify來進行通知
 * 好處是通信方式簡單,同時也可以指定等待時間
 * 使用await和countdown方法替代wait和notify
 * CountDownLatch不涉及鎖定,當count的值爲零時當前線程繼續運行
 * 當不涉及同步,只是涉及線程通信的時候,用synchronized + wait/notify就顯得太重了
 * 這時應該考慮countdownlatch/cyclicbarrier/semaphore

涉及到的方法:CountDownLatch(1),CountDown往下數,當1變爲0的時候門閂就開了,latch.countDown()調用一次數就往下-1;latch.await(),門閂的等待是不需要鎖定任何對象的;

public class Container {
    volatile List lists = new ArrayList();
    public void add(Object o){ lists.add(o); }
    public int size(){ return lists.size(); }
    public static void main(String[] args){
        Container container = new Container();
        final Object o = new Object();
        CountDownLatch latch = new CountDownLatch(1);
        new Thread(() ->{
                System.out.println("t2 啓動");
                if (container.size()!=5){
                    try {
                        latch.await();
                        // 可以指定等待時間
                        //latch.await(5000, TimeUnit.MILLISECONDS);
                        System.out.println("5個添加夠了 t1");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("t2 結束");
        }, "t2").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()-> {
            System.out.println("t1 啓動");

                for (int i = 0; i < 10; i++) {
                    container.add(new Object());
                    System.out.println("add: " + i);
                    if (container.size() == 5) {
                        // 執行 -1 後count 爲0,打開門栓,t2 執行。
                        latch.countDown();
                    }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t1 結束");
        }, "t1").start();
    }
}
輸出:
t2 啓動
t1 啓動
add: 0
add: 1
add: 2
add: 3
add: 4
5個添加夠了 t1
t2 結束
add: 5
add: 6
add: 7
add: 8
add: 9
t1 結束

原文地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章