答不上的JUC筆試題

1:有一個總任務A,分解爲子任務A1 A2 A3 ...,任何一個子任務失敗後要快速取消所有任務,請寫程序模擬。

「請尋求最優解,不要只是粗暴wait()」

本題解題思路:Fork/Join
通常使用其更專門的類型之一 RecursiveTask(可以返回結果)或 RecursiveAction。
Oracle 官方文檔:https://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html

主功能就是將一個大任務拆分成多個小任務進行處理。

處理過程中只要有個小任務失敗報錯,剩下的任務將可能被立即停止。
以下爲代碼實現:

1.1:實現代碼

public class SonTask extends RecursiveAction {

    private static final Logger logger = LoggerFactory.getLogger(SonTask.class);

    /**
     * 總共任務量
     **/
    private final int taskCount;
    /**
     * 當前task被分配的任務量
     **/
    private final int taskMete;
    /**
     * 當前task序號
     **/
    private int taskRank;
    /**
     * 每個task最大可處理任務量
     **/
    private final int maxTask = 1;

    public SonTask(int taskCount) {
        this.taskCount = taskCount;
        this.taskMete = taskCount;
    }

    private SonTask(int taskCount, int taskMete, int taskRank) {
        this.taskCount = taskCount;
        this.taskMete = taskMete;
        this.taskRank = taskRank;
    }

    @Override
    protected void compute() {
        // 任務分配量是否滿足處理條件,不滿足則將任務再拆分
        if (taskMete == maxTask) {
            printSelf();
        } else {
            List<SonTask> sonTaskList = new ArrayList<>();
            for (int i = 1; i <= taskCount; i++) {
                sonTaskList.add(new SonTask(taskCount, 1, i));
            }
            // 執行所有任務
            invokeAll(sonTaskList);
        }
    }

    /**
     * task 1 正常結束 ->
     * task 2 執行報錯 ->
     * task 3 直接終止
     **/
    private void printSelf() {
        logger.info("SON TASK RANK [{}] START", taskRank);
        try {
            TimeUnit.SECONDS.sleep(taskRank * 3);
            if (taskRank == 2) {
                logger.error("eroor occured");
                throw new RuntimeException("error");
            } else {
                logger.info("TASK [{}] OVER", taskRank);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

1.2:測試

public class StartMain {

    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool(10);

        SonTask sonTask = new SonTask(3);

        pool.invoke(sonTask);
    }
}

在task 1結束後由於task 2報錯了,task 3被取消執行。

看看ForkJoinTask#invokeAll(Collection tasks) 的源碼註釋中有這麼一句話:

If any task encounters an exception, others may be cancelled.

    /**
     * Forks all tasks in the specified collection, returning when
     * {@code isDone} holds for each task or an (unchecked) exception
     * is encountered, in which case the exception is rethrown. If
     * more than one task encounters an exception, then this method
     * throws any one of these exceptions. If any task encounters an
     * exception, others may be cancelled. However, the execution
     * status of individual tasks is not guaranteed upon exceptional
     * return. The status of each task may be obtained using {@link
     * #getException()} and related methods to check if they have been
     * cancelled, completed normally or exceptionally, or left
     * unprocessed.
     *
     * @param tasks the collection of tasks
     * @param <T> the type of the values returned from the tasks
     * @return the tasks argument, to simplify usage
     * @throws NullPointerException if tasks or any element are null
     */
    public static <T extends ForkJoinTask<?>> Collection<T> invokeAll(Collection<T> tasks) {
           ...
    }

2:請用兩個線程交替輸出A1B2C3D4...,A線程輸出字母,B線程輸出數字,要求A線程首先執行,B線程其次執行!(多種同步機制的運用)

「請尋求最優解,不要簡單的synchronized」

本題解題思路:ReentLock、Condtion
1:利用Conditon#await、Condition#aignal 進行線程之間的通信,替代Object#wait、Object#notify。
2:勿使用Thread#join 這種阻塞主線程的方式,也達不到該題的需求。

Condition的類註釋:

 /**
 * {@code Condition} factors out the {@code Object} monitor
 * methods ({@link Object#wait() wait}, {@link Object#notify notify}
 * and {@link Object#notifyAll notifyAll}) into distinct objects to
 * give the effect of having multiple wait-sets per object, by
 * combining them with the use of arbitrary {@link Lock} implementations.
 * Where a {@code Lock} replaces the use of {@code synchronized} methods
 * and statements, a {@code Condition} replaces the use of the Object
 * monitor methods.
 * ...
 */

2.1:實現代碼

public class StartMain {

    private static final Logger logger = LoggerFactory.getLogger(StartMain.class);
    private static final ReentrantLock lock = new ReentrantLock();
    private static final String[] arr = new String[]{"A1", "B2", "C3", "D4"};
    private static final AtomicInteger index = new AtomicInteger(0);

    public static void main(String[] args) {
        Condition conditionA = lock.newCondition();
        Condition conditionB = lock.newCondition();

        Thread threadA = new Thread(() -> {
            while (index.get() < arr.length) {
                try {
                    lock.lock();
                    logger.info(arr[index.get()]);
                    index.incrementAndGet();
                    conditionB.signal();
                    conditionA.await();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }, "thread-A");

        Thread threadB = new Thread(() -> {
            while (index.get() < arr.length) {
                try {
                    lock.lock();
                    conditionB.await();
                    logger.info(arr[index.get()]);
                    index.incrementAndGet();
                    conditionA.signal();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }, "thread-B");

        threadB.start();

        // 爲了使測試更加逼真,先讓B開始
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        threadA.start();
    }

}

2.2:Condition#await

    /**
     * Causes the current thread to wait until it is signalled or
     * {@linkplain Thread#interrupt interrupted}.
     *
     * <p>The lock associated with this {@code Condition} is atomically
     * released and the current thread becomes disabled for thread scheduling
     * purposes and lies dormant until <em>one</em> of four things happens:
     * 
     * ...
     * @throws InterruptedException if the current thread is interrupted
     *         (and interruption of thread suspension is supported)
     */
    void await() throws InterruptedException;

**The lock associated with this {@code Condition} is atomically released and the current thread becomes disabled **
for thread scheduling purposes and lies dormant until ...

調用了Condition.await()方法後該線程所持有的Lock鎖會被釋放掉,並且當前線程會變得不可用(阻塞),直到調用了Condtion.signal()方法。

3:華爲面試題

「請尋求最優解,不要簡單的生產者-消費者模式」

有一個生產奶酪的廠家,每天需要生產100000份奶酪賣給超市,通過一輛貨車發貨,送貨車每次送100份。
廠家有一個容量爲1000份的冷庫,用於奶酪保鮮,生產的奶酪需要先存放在冷庫,運輸車輛從冷庫取貨。
廠家有三條生產線,分別是牛奶供應生產線,發酵劑製作生產線,奶酪生產線。
生產每份奶酪需要2份牛奶和一份發酵劑。
請設計生產系統?

解題思路: BlockingDeque阻塞隊列、Atomic原子類
Oracle 官方文檔:https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/BlockingDeque.html

3.1:api註釋

1:BlockingDeque#take()

    /**
     * Retrieves and removes the head of the queue represented by this deque
     * (in other words, the first element of this deque), waiting if
     * necessary until an element becomes available.
     *
     * <p>This method is equivalent to {@link #takeFirst() takeFirst}.
     *
     * @return the head of this deque
     * @throws InterruptedException if interrupted while waiting
     */
    E take() throws InterruptedException;

該方法若從隊列中取不到元素會造成當前線程阻塞,直到拿到元素爲止。

2:BlockingDeque#put(E e)

    /**
     * Inserts the specified element into the queue represented by this deque
     * (in other words, at the tail of this deque), waiting if necessary for
     * space to become available.
     *
     * <p>This method is equivalent to {@link #putLast(Object) putLast}.
     *
     * @param e the element to add
     * @throws InterruptedException {@inheritDoc}
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this deque
     * @throws NullPointerException if the specified element is null
     * @throws IllegalArgumentException if some property of the specified
     *         element prevents it from being added to this deque
     */
    void put(E e) throws InterruptedException;

當隊列容量達到上限時,其他元素無法入隊且使當前線程阻塞,直到隊有可用空間爲止。

3.2:實現代碼

public class ProductOnlineBus {

    private static final Logger logger = LoggerFactory.getLogger(ProductOnlineBus.class);
    /**
     * 生產奶酪數量
     **/
    private final int prodNum;
    /**
     * 牛奶=奶酪*2
     **/
    private final int milkMultiple = 2;
    /**
     * 發酵劑=奶酪*1
     **/
    private final int fjjMultiple = 1;
    /**
     * 奶酪倉庫容量
     **/
    private final int cheeseCapacity = 1000;
    /**
     * 單詞運輸奶酪數量
     **/
    private final int truckCapacity = 100;
    /**
     * 總共需要運輸多少次
     **/
    private final int needTruckTimes;
    /**
     * 生產線--阻塞隊列
     **/
    private final BlockingDeque<MiikNode> milkNodeBlockingDeque;
    private final BlockingDeque<FJJNode> fjjNodeBlockingDeque;
    private final BlockingDeque<CheeseNode> cheeseNodeBlockingDeque;
    /**
     * 生產次數
     **/
    private final AtomicInteger trucked = new AtomicInteger(0);
    private final AtomicInteger milkProded = new AtomicInteger(0);
    private final AtomicInteger fjjProded = new AtomicInteger(0);
    /**
     * 實際運輸次數
     **/
    private final AtomicInteger cheeseProded = new AtomicInteger(0);

    public ProductOnlineBus(int prodNum) {
        if ((prodNum % truckCapacity) != 0) {
            throw new RuntimeException("請輸入truckCapacity的倍數");
        }
        this.prodNum = prodNum;
        this.milkNodeBlockingDeque = new LinkedBlockingDeque<>(milkMultiple);
        this.fjjNodeBlockingDeque = new LinkedBlockingDeque<>(fjjMultiple);
        this.cheeseNodeBlockingDeque = new LinkedBlockingDeque<>(cheeseCapacity);
        this.needTruckTimes = prodNum / truckCapacity;
    }

    public void starter() {
        new Thread(() -> {
            int len = prodNum * milkMultiple;
            for (int i = 0; i < len; i++) {
                try {
                    milkNodeBlockingDeque.put(new MiikNode(i));
                    milkProded.incrementAndGet();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "MilkThread").start();

        new Thread(() -> {
            int len = prodNum * fjjMultiple;
            for (int i = 0; i < len; i++) {
                try {
                    fjjNodeBlockingDeque.put(new FJJNode(i));
                    fjjProded.incrementAndGet();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "FJJThread").start();

        new Thread(() -> {
            for (int i = 0; i < prodNum; i++) {
                try {
                    for (int j = 0; j < milkMultiple; j++) {
                        milkNodeBlockingDeque.take();
                    }
                    for (int j = 0; j < fjjMultiple; j++) {
                        fjjNodeBlockingDeque.take();
                    }
                    cheeseNodeBlockingDeque.put(new CheeseNode(i));
                    cheeseProded.incrementAndGet();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "CheeseThread").start();

        new Thread(() -> {
            while (trucked.get() < needTruckTimes) {
                try {
                    for (int i = 0; i < truckCapacity; i++) {
                        cheeseNodeBlockingDeque.take();
                    }
                    trucked.incrementAndGet();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            logger.info("over of->cheese:[{}],milk:[{}],fjj[{}],truck:[{}]",
                    cheeseProded.get(), milkProded.get(), fjjProded.get(), trucked.get());
        }, "TruckThread").start();
    }

    /**
     * 牛奶
     **/
    private class MiikNode {
        public MiikNode(int seq) {
            logger.info("生產牛奶[{}]...", seq);
        }
    }

    /**
     * 發酵劑
     **/
    private class FJJNode {
        public FJJNode(int seq) {
            logger.info("生產發酵劑[{}]...", seq);
        }
    }

    /**
     * 奶酪
     **/
    private class CheeseNode {
        public CheeseNode(int seq) {
            logger.info("生產奶酪[{}]...", seq);
        }
    }

}

3.3:運行

public class StartMain {

     public static void main(String[] args) {
        ProductOnlineBus pb = new ProductOnlineBus(100000);
        pb.starter();
    }
}

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