讓線程按順序執行8種方法 (轉)

出處:讓線程按順序執行8種方法html

 

一.前言


本文使用了7中方法實如今多線程中讓線程按順序運行的方法,涉及到多線程中許多經常使用的方法,不止爲了知道如何讓線程按順序運行,更是讓讀者對多線程的使用有更深入的瞭解。 使用的方法以下:java

  • [1] 使用線程的join方法
  • [2] 使用主線程的join方法
  • [3] 使用線程的wait方法
  • [4] 使用線程的線程池方法
  • [5] 使用線程的Condition(條件變量)方法
  • [6] 使用線程的CountDownLatch(倒計數)方法
  • [7] 使用線程的CyclicBarrier(迴環柵欄)方法
  • [8] 使用線程的Semaphore(信號量)方法

二.實現


咱們下面須要完成這樣一個應用場景:數據庫

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

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

1.使用線程的join方法

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

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

/**
 * 經過子程序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()來實現對線程的阻塞。post

/**
 * 經過主程序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;

/**
 * 經過SingleThreadExecutor讓線程按順序執行
 */
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("領導吩咐:");
        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方法被帶調用)時 ,這些等待線程纔會被喚醒,從而從新爭奪鎖。

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

/**
 * 使用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.使用線程的CountDownLatch(倒計數)方法

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;

/**
 * @author wwj
 * 使用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();
    }
}

運行結果

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

總結

看完了這麼多種方法,是否是對多線程有了更深刻的瞭解呢?不妨本身試試吧(代碼拷貝都可運行)

使用的場景還有不少,根據開發需求場景,選擇合適的方法,達到事半功倍的效果。

相關文章
相關標籤/搜索