Java併發系列之二 一道好玩有趣的多線程面試題

1. 一道好玩有趣的面試題

Java求職中往往會碰到這樣的面試題。請實現多線程交替打印0和1的程序。

看到這道題目,憑着程序員的第六感,我們很容易聯想到消費者和生產者模式。他們之間有很大的相似之處啊。生產者線程負責往倉庫生產物資,當倉庫的物資物滿爲患的時候,阻塞線程停止生產,等待消費者線程消費物資後喚醒生產者線程。消費者線程負責從倉庫拿出物資,當倉庫沒有物資的時候阻塞線程停止消費,等待生產者線程生產物資後喚醒消費者線程。這道題目不也是一樣嗎,有這樣兩類線程,一類線程打印0(下文稱T0),一類線程打印1(下文稱T1)。當滿足打印0的條件下,T0打印0,同時阻塞T0並喚醒T1線程,在不滿足打印0的條件下,T0就一直等待被T1喚醒。同理T1線程也是一樣,在滿足打印1的條件下,T1打印1,同時阻塞T1並喚醒T0線程,在不滿足打印1的條件下,T1就一直等待被T0喚醒。要實現這樣的功能我們很容易想到 wait()和notify()/notifyAll()方法。

  1. wait、notify以及notifyAll都是Object對象的方法,他們必須在被 synchronized 同步的方法或代碼塊中調用,否則會報錯
  2. 調用wait方法會使該線程進入等待狀態,並且會釋放被同步對象的鎖
  3. notify操作可以喚醒一個因執行wait而處於阻塞狀態的線程,使其進入就緒狀態,被喚醒的線程會去嘗試着獲取對象鎖,然後執行wait之後的代碼。如果發出notify操作時,沒有線程處於阻塞狀態,那麼該命令會忽略。注意執行notify並不會馬上釋放對象鎖,會等到執行完該同步方法或同步代碼塊後才釋放
  4. notifyAll方法可以喚醒等待隊列中等待同一共享資源的“全部”線程從等待狀態退出,進入可運行狀態。此時,優先級最高的那個線程優先執行,但也有可能是隨機執行,這取決於JVM虛擬機的實現

2. 用synchronized配合wait() notify()/notifyAll()實現

下面我就用wait() notify()來實現交替打印功能

package com.peter.tips.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 *
 * Created by jiangbin on 2018/6/3.
 */
public class AlternatesUseWait {

    private static final Object lock = new Object();
    private static int value;

    //判斷當前應該打印的值
    private static int getCurrentValue() {
        return value % 2;
    }

    public static void main(String[] args) {
        new Thread(new PrintZeroTask()).start();
        new Thread(new PrintOneTask()).start();
    }

    public static class PrintZeroTask implements Runnable {

        @Override
        public void run() {
            try {
                synchronized (lock) {
                    while (true) {//這個while循環是讓線程一直打印
                        while (getCurrentValue() == 1)
                        {//大家一定要注意,這個while循環必不可少,少了這個while循環,這道面試題基本就是不及格
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        try {
                            TimeUnit.MILLISECONDS.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("0 " + Thread.currentThread().getName());
                        value = (value + 1) % 2;//value 交替
                        lock.notify();//喚醒打印1的線程
                    }
                }
            } finally {
            }
        }
    }

    public static class PrintOneTask implements Runnable {

        @Override
        public void run() {
            try {
                synchronized (lock) {
                    while (true) {
                        while (getCurrentValue() == 0) {
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        try {
                            TimeUnit.MILLISECONDS.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("1 " + Thread.currentThread().getName());
                        value = (value + 1) % 2;
                        lock.notify();
                    }
                }
            } finally {
            }
        }
    }
}

至於打印的結果麻煩大家親自運行下

2. 用Lock配合await() signal()/signalAll()實現

上一節我們講到了java concurrent包的ReentrantLock。既然用synchronized 配合 wait/notify能實現。那麼ReentrantLock 是不是也有類似wait/notify這樣的實現呢。答案是有的。Lock接口有個Condition newCondition()方法,Condition是一個接口,它有await()和signal()/signalAll()。跟Object的wait() notify()/notifyAll()剛好一一對應。

package com.peter.tips.lock;

import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 多線程交替打印0和1
 * Created by jiangbin on 2018/6/3.
 */
public class Alternates {
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition printOneCondition = lock.newCondition();
    private static Condition printTwoCondition = lock.newCondition();
    private static  int value;

    private static int getCurrentValue() {
        return value % 2;
    }

    public static void main(String[] args) {
        new Thread(new PrintZeroTask()).start();
        new Thread(new PrintOneTask()).start();
    }

    public static class PrintZeroTask implements Runnable {

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName());
                lock.lock();
                while (true) {
                    while (getCurrentValue() == 1) {
                        try {
                            printOneCondition.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    try {
                        TimeUnit.MILLISECONDS.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("0 "+Thread.currentThread().getName());
                    value = (value + 1) % 2;
                    printTwoCondition.signal();
                }
            } finally {
                lock.unlock();
            }
        }
    }

    public static class PrintOneTask implements Runnable {

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName());

                lock.lock();
                while (true) {
                    while (getCurrentValue() == 0) {
                        try {
                            printTwoCondition.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    try {
                        TimeUnit.MILLISECONDS.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("1 "+Thread.currentThread().getName());
                    value = (value + 1) % 2;
                    printOneCondition.signal();
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

代碼已上傳至 https://github.com/lizijin/tips.git

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