Java並發編程原理與實戰六:主線程等待子線程解決方案


本文將研究的是主線程等待所有子線程執行完成之后再繼續往下執行的解決方案

public class TestThread extends Thread  
{  
    public void run()  
    {  
        System.out.println(this.getName() + "子線程開始");  
        try  
        {  
            // 子線程休眠五秒  
            Thread.sleep(5000);  
        }  
        catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        }  
        System.out.println(this.getName() + "子線程結束");  
    }  
}  

首先是一個線程,它執行完成需要5秒。

1、主線程等待一個子線程

public class Main  
{  
    public static void main(String[] args)  
    {  
        long start = System.currentTimeMillis();  
          
        Thread thread = new TestThread();  
        thread.start();  
          
        long end = System.currentTimeMillis();  
        System.out.println("子線程執行時長:" + (end - start));  
    }  
}  

在主線程中,需要等待子線程執行完成。但是執行上面的main發現並不是想要的結果:

子線程執行時長:0
Thread-0子線程開始
Thread-0子線程結束

很明顯主線程和子線程是並發執行的,主線程並沒有等待。

對於只有一個子線程,如果主線程需要等待子線程執行完成,再繼續向下執行,可以使用Thread的join()方法。join()方法會阻塞主線程繼續向下執行。

public class Main  
{  
    public static void main(String[] args)  
    {  
        long start = System.currentTimeMillis();  
          
        Thread thread = new TestThread();  
        thread.start();  
          
        try  
        {  
            thread.join();  
        }  
        catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        }  
          
        long end = System.currentTimeMillis();  
        System.out.println("子線程執行時長:" + (end - start));  
    }  
}  

執行結果:

Thread-0子線程開始
Thread-0子線程結束
子線程執行時長:5000

注意:join()要在start()方法之后調用。

2、主線程等待多個子線程

比如主線程需要等待5個子線程。這5個線程之間是並發執行。

public class Main  
{  
    public static void main(String[] args)  
    {  
        long start = System.currentTimeMillis();  
          
        for(int i = 0; i < 5; i++)  
        {  
            Thread thread = new TestThread();  
            thread.start();  
              
            try  
            {  
                thread.join();  
            }  
            catch (InterruptedException e)  
            {  
                e.printStackTrace();  
            }  
        }  
          
        long end = System.currentTimeMillis();  
        System.out.println("子線程執行時長:" + (end - start));  
    }  
}  

在上面的代碼套上一個for循環,執行結果:

Thread-0子線程開始
Thread-0子線程結束
Thread-1子線程開始
Thread-1子線程結束
Thread-2子線程開始
Thread-2子線程結束
Thread-3子線程開始
Thread-3子線程結束
Thread-4子線程開始
Thread-4子線程結束
子線程執行時長:25000

由於thread.join()阻塞了主線程繼續執行,導致for循環一次就需要等待一個子線程執行完成,而下一個子線程不能立即start(),5個子線程不能並發。

要想子線程之間能並發執行,那么需要在所有子線程start()后,在執行所有子線程的join()方法。

public class Main  
{  
    public static void main(String[] args)  
    {  
        long start = System.currentTimeMillis();  
          
        List<Thread> list = new ArrayList<Thread>();  
        for(int i = 0; i < 5; i++)  
        {  
            Thread thread = new TestThread();  
            thread.start();  
            list.add(thread);  
        }  
          
        try  
        {  
            for(Thread thread : list)  
            {  
                thread.join();  
            }  
        }  
        catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        }  
          
        long end = System.currentTimeMillis();  
        System.out.println("子線程執行時長:" + (end - start));  
    }  
}  

執行結果:

Thread-0子線程開始
Thread-3子線程開始
Thread-1子線程開始
Thread-2子線程開始
Thread-4子線程開始
Thread-3子線程結束
Thread-0子線程結束
Thread-2子線程結束
Thread-1子線程結束
Thread-4子線程結束
子線程執行時長:5000

3、主線程等待多個子線程(CountDownLatch實現)

CountDownLatch是Java.util.concurrent中的一個同步輔助類,可以把它看做一個倒數計數器,就像神舟十號發射時倒數:10,9,8,7….2,1,0,走你。初始化時先設置一個倒數計數初始值,每調用一次countDown()方法,倒數值減一,await()方法會阻塞當前進程,直到倒數至0。

同樣還是主線程等待5個並發的子線程。修改上面的代碼,在主線程中,創建一個初始值為5的CountDownLatch,並傳給每個子線程,在每個子線程最后調用countDown()方法對倒數器減1,當5個子線程等執行完成,那么CountDownLatch也就倒數完成,主線程調用await()方法等待5個子線程執行完成。

修改MyThread接收傳入的CountDownLatch:

public class TestThread extends Thread    
{    
    private CountDownLatch countDownLatch;    
            
    public TestThread(CountDownLatch countDownLatch)    
    {    
        this.countDownLatch = countDownLatch;    
    }    
  
    public void run()    
    {    
        System.out.println(this.getName() + "子線程開始");    
        try    
        {    
            // 子線程休眠五秒    
            Thread.sleep(5000);    
        }    
        catch (InterruptedException e)    
        {    
            e.printStackTrace();    
        }  
  
        System.out.println(this.getName() + "子線程結束");  
            
        // 倒數器減1  
        countDownLatch.countDown();  
    }  
}

修改main:

public class Main  
{  
    public static void main(String[] args)  
    {  
        long start = System.currentTimeMillis();  
          
        // 創建一個初始值為5的倒數計數器  
        CountDownLatch countDownLatch = new CountDownLatch(5);  
        for(int i = 0; i < 5; i++)  
        {  
            Thread thread = new TestThread(countDownLatch);  
            thread.start();  
        }  
          
        try  
        {  
            // 阻塞當前線程,直到倒數計數器倒數到0  
            countDownLatch.await();  
        }  
        catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        }  
          
        long end = System.currentTimeMillis();  
        System.out.println("子線程執行時長:" + (end - start));  
    }  
}  

執行結果:

Thread-0子線程開始
Thread-2子線程開始
Thread-1子線程開始
Thread-3子線程開始
Thread-4子線程開始
Thread-2子線程結束
Thread-4子線程結束
Thread-1子線程結束
Thread-0子線程結束
Thread-3子線程結束
子線程執行時長:5000

注意:如果子線程中會有異常,那么countDownLatch.countDown()應該寫在finally里面,這樣才能保證異常后也能對計數器減1,不會讓主線程永遠等待。

另外,await()方法還有一個實用的重載方法:public booleanawait(long timeout, TimeUnit unit),設置超時時間。

例如上面的代碼,想要設置超時時間10秒,到了10秒無論是否倒數完成到0,都會不再阻塞主線程。返回值是boolean類型,如果是超時返回false,如果計數到達0沒有超時返回true。

// 設置超時時間為10秒  
boolean timeoutFlag = countDownLatch.await(10,TimeUnit.SECONDS);  
if(timeoutFlag)  
{  
    System.out.println("所有子線程執行完成");  
}  
else  
{  
    System.out.println("超時");  
}  

4、主線程等待線程池

Java線程池java.util.concurrent.ExecutorService是很好用的多線程管理方式。ExecutorService的一個方法boolean awaitTermination(long timeout, TimeUnit unit),即阻塞主線程,等待線程池的所有線程執行完成,用法和上面所說的CountDownLatch的public boolean await(long timeout,TimeUnit unit)類似,參數設置一個超時時間,返回值是boolean類型,如果超時返回false,如果線程池中的線程全部執行完成,返回true。

由於ExecutorService沒有類似CountDownLatch的無參數的await()方法,只能通過awaitTermination來實現主線程等待線程池。

public class Main  
{  
    public static void main(String[] args)  
    {  
        long start = System.currentTimeMillis();  
          
        // 創建一個同時允許兩個線程並發執行的線程池  
        ExecutorService executor = Executors.newFixedThreadPool(2);  
        for(int i = 0; i < 5; i++)  
        {  
            Thread thread = new TestThread();  
            executor.execute(thread);  
        }  
        executor.shutdown();  
          
        try  
        {  
            // awaitTermination返回false即超時會繼續循環,返回true即線程池中的線程執行完成主線程跳出循環往下執行,每隔10秒循環一次  
            while (!executor.awaitTermination(10, TimeUnit.SECONDS));  
        }  
        catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        }  
          
        long end = System.currentTimeMillis();  
        System.out.println("子線程執行時長:" + (end - start));  
    }  
} 

執行結果:

Thread-0子線程開始
Thread-1子線程開始
Thread-0子線程結束
Thread-2子線程開始
Thread-1子線程結束
Thread-3子線程開始
Thread-2子線程結束
Thread-4子線程開始
Thread-3子線程結束
Thread-4子線程結束
子線程執行時長:15000

另外,while(!executor.isTerminated())也可以替代上面的while (!executor.awaitTermination(10,TimeUnit.SECONDS)),isTerminated()是用來判斷線程池是否執行完成。但是二者比較我認為還是awaitTermination更好,它有一個超時時間可以控制每隔多久循環一次,而不是一直在循環來消耗性能。

5.其它方案參考:

解決方案1:
基本思路是這樣:
每個SubThread子線程類實例有個自己的狀態99-初始化  0-執行成功 1-執行失敗,當執行完畢之后,將狀態修改為0或者1
MainThread主線程類中有個List,用來登記所有子線程。子線程的創建通過主線程來創建,每次創建之后,都會將子線程添加到List中。
所有子線程創建完成之后,通過主線程的start方法啟動所有子線程,並通過一個while循環來遍歷List中的所有子線程的狀態,
判斷是否存在狀態為99的,如果沒有,則便是全部子線程執行完畢
/**
 * 子線程類
 * @author Administrator
 *
 */
public class SubThread implements Runnable{
    
    private int status = 99; //99-初始化  0-執行成功 1-執行失敗 
 
    public void run() {
        System.out.println("開始執行...");
        try{
            Thread.sleep(2000);
        }catch(Exception e){
            e.printStackTrace();
        }
        status=0;
        System.out.println("執行完畢...");
    }
 
    public int getStatus() {
        return status;
    }
 
    public void setStatus(int status) {
        this.status = status;
    }
}  
/**
 * 主線程類
 * @author Administrator
 *
 */
public class MainThread {
    private List<SubThread> subThreadList = new ArrayList<SubThread>();
    
    /**
     * 創建子線程
     * @return
     */
    public SubThread createSubThread(){
        SubThread subThread = new SubThread();
        subThreadList.add(new SubThread());
        return subThread;
    }
    
    public boolean start(){
        for(SubThread subThread : subThreadList){
            new Thread(subThread).start();
        }
        
        /**
         * 監控所有子線程是否執行完畢
         */
        boolean continueFlag = true;
        while(continueFlag){
            for(SubThread subThread : subThreadList){
                if(subThread.getStatus()==99){
                    continueFlag = true;
                    break;
                }
                continueFlag = false;
            }
        }
        
        /**
         * 判斷子線程的執行結果
         */
        boolean result = true;
        for(SubThread subThread : subThreadList){
            if(subThread.getStatus()!=0){
                result = false;
                break;
            }
        }
        
        return result;
    }
 
}  

測試代碼:

   public static void main(String[] args) {
        MainThread main = new MainThread();
        main.createSubThread();
        main.createSubThread();
        main.createSubThread();
        boolean result = main.start();
        System.out.println(result);
    }
解決方案2:
通過計數器方式解決。基本思路如下:
計數器類CountLauncher負責記錄正在執行的子線程的總數,所有的子線程共享該計數器類對象,當子線程執行完畢之后,調用計數器的counDown()方法進行計數器減1.
主線程通過計數器類來判斷是否所有子線程都執行完畢。
/**
 * 計數器類
 * @author Administrator
 *
 */
public class CountLauncher {
    private int count;
    
    public CountLauncher(int count){
        this.count = count;
    }
    
    public synchronized void countDown(){
        count --;
    }
 
    public int getCount() {
        return count;
    }
 
    public void setCount(int count) {
        this.count = count;
    }
}  
/**
 * 子線程類
 * @author Administrator
 *
 */
public class SubThread implements Runnable{
    
    /**
     * 計數器類對象實例
     */
    private CountLauncher countLauncher;
    
    private int status = 99; //99-初始化  0-執行成功 1-執行失敗 
    
 
    public void run() {
        System.out.println("開始執行...");
        try{
            Thread.sleep(2000);
        }catch(Exception e){
            e.printStackTrace();
        }
        status=0;
        System.out.println("執行完畢...");
        countLauncher.countDown();
    }
 
    public int getStatus() {
        return status;
    }
 
    public void setStatus(int status) {
        this.status = status;
    }
 
    public CountLauncher getCountLauncher() {
        return countLauncher;
    }
 
    public void setCountLauncher(CountLauncher countLauncher) {
        this.countLauncher = countLauncher;
    }
    
}  
/**
 * 主線程類
 * @author Administrator
 *
 */
public class MainThread {
    private List<SubThread> subThreadList = new ArrayList<SubThread>();
    
    /**
     * 創建子線程
     * @return
     */
    public synchronized SubThread createSubThread(){
        SubThread subThread = new SubThread();
        subThreadList.add(new SubThread());
        return subThread;
    }
    
    
    
    public boolean start(){
        CountLauncher countLauncher = new CountLauncher(subThreadList.size());
        for(SubThread subThread : subThreadList){
            subThread.setCountLauncher(countLauncher);
            new Thread(subThread).start();
        }
        
        while(countLauncher.getCount()>0){
            System.out.println(countLauncher.getCount());
        }
        
        /**
         * 判斷子線程的執行結果
         */
        boolean result = true;
        for(SubThread subThread : subThreadList){
            if(subThread.getStatus()!=0){
                result = false;
                break;
            }
        }
        
        return result;
    }
    
    /**
     * 測試實例
     */
    public static void main(String[] args) {
        
        MainThread main = new MainThread();
        main.createSubThread();
        main.createSubThread();
        main.createSubThread();
        boolean result = main.start();
        System.out.println(result);
    }
 
}  
六.總結
解決方案(推薦):
使用的是Java自帶的計數器類java.util.concurrent.CountDownLatch。
還有一點就是不需要在主線程中通過while來監控所有子線程,是否通過調用它的await方法進行等待所有子線程的執行完畢。
使用計數器時,需要注意的一點是:子線程中調用countDown()方法時一定要放在最后來執行,否則會出現子線程未執行完畢,主線程就開始往下執行了。
因為一定計數器為0,就會自動喚醒主線程的。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM