實現線程順序執行的各種方法總結

一、實現方法總覽

  1. 使用線程的 join 方法
  2. 使用主線程的 join 方法
  3. 使用線程的 wait 方法
  4. 使用線程的線程池方法
  5. 使用線程的 Condition(條件變量) 方法
  6. 使用線程的 CuDownLatch(倒計數) 方法
  7. 使用 CyclicBarrier (迴環柵欄)實現線程按順序執行
  8. 使用線程的 Sephmore(信號量) 實現線程按順序執行

二、具體實現

我們下面需要完成這樣一個應用場景:

1.早上測試人員、產品經理、開發人員陸續的來公司上班;
2.產品經理規劃新需求;
3.開發人員開發新需求功能;
4.測試人員測試新功能。

規劃需求,開發需求新功能,測試新功能是一個有順序的,我們把thread1看做產品經理,thread2看做開發人員,thread3看做測試人員。

1. 使用線程的 join 方法

join(): 是Theard的方法,作用是調用線程需等待該join()線程執行完成後,才能繼續用下運行。

應用場景: 當一個線程必須等待另一個線程執行完畢才能執行時可以使用join方法。

/**
 * 通過子程序join使線程按順序執行
 */
public class ThreadJoinDemo {

    public static void main(String[] args) {
        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("產品經理規劃新需求");
            }
        });

        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    thread1.join();
                    System.out.println("開發人員開發新需求功能");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    thread2.join();
                    System.out.println("測試人員測試新功能");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        System.out.println("早上:");
        System.out.println("測試人員來上班了...");
        thread3.start();
        System.out.println("產品經理來上班了...");
        thread1.start();
        System.out.println("開發人員來上班了...");
        thread2.start();
    }
}

運行結果如下:

早上:測試人員來上班了… 
產品經理來上班了…
開發人員來上班了… 
產品經理規劃新需求 
開發人員開發新需求功能 
測試人員測試新功能

2. 使用主線程的 join 方法

先在主線程中使用join()來實現對線程的阻塞。

/**
 * 通過主程序join使線程按順序執行
 */
public class ThreadMainJoinDemo {
    public static void main(String[] args) throws Exception {

        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("產品經理正在規劃新需求...");
            }
        });

        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("開發人員開發新需求功能");
            }
        });

        final Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("測試人員測試新功能");
            }
        });

        System.out.println("早上:");
        System.out.println("產品經理來上班了");
        System.out.println("測試人員來上班了");
        System.out.println("開發人員來上班了");
        thread1.start();
        //在父進程調用子進程的join()方法後,父進程需要等待子進程運行完再繼續運行。
        System.out.println("開發人員和測試人員休息會...");
        thread1.join();
        System.out.println("產品經理新需求規劃完成!");
        thread2.start();
        System.out.println("測試人員休息會...");
        thread2.join();
        thread3.start();
    }
}

運行結果如下:

早上:
產品經理來上班了
測試人員來上班了
開發人員來上班了
開發人員和測試人員休息會...
產品經理正在規劃新需求...
產品經理新需求規劃完成!
測試人員休息會...
開發人員開發新需求功能
測試人員測試新功能

3. 使用線程的 wait 方法

wait(): 是Object的方法,作用是讓當前線程進入等待狀態,同時,wait()也會讓當前線程釋放它所持有的鎖。“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法”,當前線程被喚醒(進入“就緒狀態”)

notify()和notifyAll(): 是Object的方法,作用則是喚醒當前對象上的等待線程;notify()是喚醒單個線程,而notifyAll()是喚醒所有的線程。

wait(long timeout): 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的notify()方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”)。

應用場景: Java實現生產者消費者的方式。

public class ThreadWaitDemo {

    private static Object myLock1 = new Object();
    private static Object myLock2 = new Object();

    /**
     * 爲什麼要加這兩個標識狀態?
     * 如果沒有狀態標識,當t1已經運行完了t2才運行,t2在等待t1喚醒導致t2永遠處於等待狀態
     */
    private static Boolean t1Run = false;
    private static Boolean t2Run = false;
    public static void main(String[] args) {

        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (myLock1){
                    System.out.println("產品經理規劃新需求...");
                    t1Run = true;
                    myLock1.notify();
                }
            }
        });

        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (myLock1){
                    try {
                        if(!t1Run){
                            // System.out.println("開發人員先休息會...");
                            myLock1.wait();
                        }
                        synchronized (myLock2){
                            System.out.println("開發人員開發新需求功能");
                            myLock2.notify();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (myLock2){
                    try {
                        if(!t2Run){
                            // System.out.println("測試人員先休息會...");
                            myLock2.wait();
                        }
                        System.out.println("測試人員測試新功能");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        System.out.println("早上:");
        System.out.println("測試人員來上班了...");
        thread3.start();
        System.out.println("產品經理來上班了...");
        thread1.start();
        System.out.println("開發人員來上班了...");
        thread2.start();
    }
}

運行結果如下:

早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
產品經理規劃新需求...
開發人員開發新需求功能
測試人員測試新功能

4. 使用線程的線程池方法

JAVA通過Executors提供了四種線程池

單線程化線程池(newSingleThreadExecutor);
可控最大併發數線程池(newFixedThreadPool);
可回收緩存線程池(newCachedThreadPool);
支持定時與週期性任務的線程池(newScheduledThreadPool)。
單線程化線程池(newSingleThreadExecutor): 優點,串行執行所有任務。

submit(): 提交任務。

shutdown(): 方法用來關閉線程池,拒絕新任務。

應用場景: 串行執行所有任務。如果這個唯一的線程因爲異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {

    static ExecutorService executorService = Executors.newSingleThreadExecutor();

    public static void main(String[] args) throws Exception {

        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("產品經理規劃新需求");
            }
        });

        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("開發人員開發新需求功能");
            }
        });

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("測試人員測試新功能");
            }
        });

        System.out.println("早上:");
        System.out.println("產品經理來上班了");
        System.out.println("測試人員來上班了");
        System.out.println("開發人員來上班了");
        // System.out.println("首先,產品經理規劃新需求...");
        executorService.submit(thread1);
        // System.out.println("然後,開發人員開發新需求功能...");
        executorService.submit(thread2);
        // System.out.println("最後,測試人員測試新功能...");
        executorService.submit(thread3);
        executorService.shutdown();
    }
}

運行結果:

早上:
產品經理來上班了
測試人員來上班了
開發人員來上班了
產品經理規劃新需求
開發人員開發新需求功能
測試人員測試新功能

5. 使用線程的 Condition(條件變量) 方法

Condition(條件變量): 通常與一個鎖關聯。需要在多個Contidion中共享一個鎖時,可以傳遞一個Lock/RLock實例給構造方法,否則它將自己生成一個RLock實例。

Condition中await()方法類似於Object類中的wait()方法。
Condition中await(long time,TimeUnit unit)方法類似於Object類中的wait(long time)方法。
Condition中signal()方法類似於Object類中的notify()方法。
Condition中signalAll()方法類似於Object類中的notifyAll()方法。
應用場景: Condition是一個多線程間協調通信的工具類,使得某個,或者某些線程一起等待某個條件(Condition),只有當該條件具備( signal 或者 signalAll方法被調用)時 ,這些等待線程纔會被喚醒,從而重新爭奪鎖。

/**
 * 使用Condition(條件變量)實現線程按順序運行
 */
public class ThreadConditionDemo {

    private static Lock lock = new ReentrantLock();
    private static Condition condition1 = lock.newCondition();
    private static Condition condition2 = lock.newCondition();

    /**
     * 爲什麼要加這兩個標識狀態?
     * 如果沒有狀態標識,當t1已經運行完了t2才運行,t2在等待t1喚醒導致t2永遠處於等待狀態
     */
    private static Boolean t1Run = false;
    private static Boolean t2Run = false;

    public static void main(String[] args) {

        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("產品經理規劃新需求");
                t1Run = true;
                condition1.signal();
                lock.unlock();
            }
        });

        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    if(!t1Run){
                        System.out.println("開發人員先休息會...");
                        condition1.await();
                    }
                    System.out.println("開發人員開發新需求功能");
                    t2Run = true;
                    condition2.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.unlock();
            }
        });

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    if(!t2Run){
                        System.out.println("測試人員先休息會...");
                        condition2.await();
                    }
                    System.out.println("測試人員測試新功能");
                    lock.unlock();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        System.out.println("早上:");
        System.out.println("測試人員來上班了...");
        thread3.start();
        System.out.println("產品經理來上班了...");
        thread1.start();
        System.out.println("開發人員來上班了...");
        thread2.start();
    }
}

運行結果:

早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
產品經理規劃新需求
測試人員先休息會...
開發人員開發新需求功能
測試人員測試新功能

6. 使用線程的 CuDownLatch(倒計數) 方法

CountDownLatch: 位於java.util.concurrent包下,利用它可以實現類似計數器的功能。

應用場景: 比如有一個任務C,它要等待其他任務A,B執行完畢之後才能執行,此時就可以利用CountDownLatch來實現這種功能了。

import java.util.concurrent.CountDownLatch;

/**
 * 通過CountDownLatch(倒計數)使線程按順序執行
 */
public class ThreadCountDownLatchDemo {

    /**
     * 用於判斷線程一是否執行,倒計時設置爲1,執行後減1
     */
    private static CountDownLatch c1 = new CountDownLatch(1);

    /**
     * 用於判斷線程二是否執行,倒計時設置爲1,執行後減1
     */
    private static CountDownLatch c2 = new CountDownLatch(1);

    public static void main(String[] args) {
        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("產品經理規劃新需求");
                //對c1倒計時-1
                c1.countDown();
            }
        });

        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //等待c1倒計時,計時爲0則往下運行
                    c1.await();
                    System.out.println("開發人員開發新需求功能");
                    //對c2倒計時-1
                    c2.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //等待c2倒計時,計時爲0則往下運行
                    c2.await();
                    System.out.println("測試人員測試新功能");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        System.out.println("早上:");
        System.out.println("測試人員來上班了...");
        thread3.start();
        System.out.println("產品經理來上班了...");
        thread1.start();
        System.out.println("開發人員來上班了...");
        thread2.start();
    }
}

運行結果:

早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
產品經理規劃新需求
開發人員開發新需求功能
測試人員測試新功能

7. 使用 CyclicBarrier (迴環柵欄)實現線程按順序執行

CyclicBarrier(迴環柵欄): 通過它可以實現讓一組線程等待至某個狀態之後再全部同時執行。叫做迴環是因爲當所有等待線程都被釋放以後,CyclicBarrier可以被重用。我們暫且把這個狀態就叫做barrier,當調用await()方法之後,線程就處於barrier了。

應用場景: 公司組織春遊,等待所有的員工到達集合地點才能出發,每個人到達後進入barrier狀態。都到達後,喚起大家一起出發去旅行。

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * 使用CyclicBarrier(迴環柵欄)實現線程按順序運行
 */
public class CyclicBarrierDemo {

    static CyclicBarrier barrier1 = new CyclicBarrier(2);
    static CyclicBarrier barrier2 = new CyclicBarrier(2);

    public static void main(String[] args) {

        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("產品經理規劃新需求");
                    //放開柵欄1
                    barrier1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        });

        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //放開柵欄1
                    barrier1.await();
                    System.out.println("開發人員開發新需求功能");
                    //放開柵欄2
                    barrier2.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        });

        final Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //放開柵欄2
                    barrier2.await();
                    System.out.println("測試人員測試新功能");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        });

        System.out.println("早上:");
        System.out.println("測試人員來上班了...");
        thread3.start();
        System.out.println("產品經理來上班了...");
        thread1.start();
        System.out.println("開發人員來上班了...");
        thread2.start();
    }
}

運行結果:

早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
產品經理規劃新需求
開發人員開發新需求功能
測試人員測試新功能

8. 使用線程的 Sephmore(信號量) 實現線程按順序執行

Sephmore(信號量): Semaphore是一個計數信號量,從概念上將,Semaphore包含一組許可證,如果有需要的話,每個acquire()方法都會阻塞,直到獲取一個可用的許可證,每個release()方法都會釋放持有許可證的線程,並且歸還Semaphore一個可用的許可證。然而,實際上並沒有真實的許可證對象供線程使用,Semaphore只是對可用的數量進行管理維護。

acquire(): 當前線程嘗試去阻塞的獲取1個許可證,此過程是阻塞的,當前線程獲取了1個可用的許可證,則會停止等待,繼續執行。

release(): 當前線程釋放1個可用的許可證。

應用場景: Semaphore可以用來做流量分流,特別是對公共資源有限的場景,比如數據庫連接。假設有這個的需求,讀取幾萬個文件的數據到數據庫中,由於文件讀取是IO密集型任務,可以啓動幾十個線程併發讀取,但是數據庫連接數只有10個,這時就必須控制最多隻有10個線程能夠拿到數據庫連接進行操作。這個時候,就可以使用Semaphore做流量控制。

import java.util.concurrent.Semaphore;

/**
 * 使用Sephmore(信號量)實現線程按順序運行
 */
public class SemaphoreDemo {
    private static Semaphore semaphore1 = new Semaphore(1);
    private static Semaphore semaphore2 = new Semaphore(1);
    public static void main(String[] args) {
        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("產品經理規劃新需求");
                semaphore1.release();
            }
        });

        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore1.acquire();
                    System.out.println("開發人員開發新需求功能");
                    semaphore2.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore2.acquire();
                    thread2.join();
                    semaphore2.release();
                    System.out.println("測試人員測試新功能");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        System.out.println("早上:");
        System.out.println("測試人員來上班了...");
        thread3.start();
        System.out.println("產品經理來上班了...");
        thread1.start();
        System.out.println("開發人員來上班了...");
        thread2.start();
    }
}

運行結果:

早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
產品經理規劃新需求
開發人員開發新需求功能
測試人員測試新功能

三、總結

根據開發需求場景,選擇合適的方法,達到事半功倍的效果。
知識還是要活學活用纔會理解的更深刻,當然,也只有對知識理解的夠深刻才能夠活學活用。



參考原文:
https://mp.weixin.qq.com/s/KBwqxNmP8DT4zQlpKqTPCQ?from=singlemessage&scene=1&subscene=10000&clicktime=1582985692&enterid=1582985692

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