天天看點

5種判斷線程池任務執行完成的方式

作者:Java碼農之路

Thread線程是否執行完成,我們可以調用join方法然後等待線程執行完成;那在使用線程池的時候,我們如何知道線程已經執行完成了?本文就帶給大家五種判斷的方式:

  • isTerminated() 方式,在執行 shutdown() ,關閉線程池後,判斷是否所有任務已經完成。
  • ThreadPoolExecutor 的 getCompletedTaskCount() 方法,判斷完成任務數和全部任務數是否相等。
  • CountDownLatch計數器,使用閉鎖計數來判斷是否全部完成。
  • 手動維護一個公共計數 ,原理和閉鎖類似,就是更加靈活。
  • 使用submit向線程池送出任務,Future判斷任務執行狀态。

方法一:isTerminated()

測試代碼

package pool;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author 百裡
 */
public class BaiLiIsShutdownThreadPoolDemo {
    /**
     * 建立一個最大線程數15的線程池
     */
    public static ThreadPoolExecutor pool = new ThreadPoolExecutor(
            10,
            15,
            0L,
            TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(10));
    /**
     * 線程執行方法,随機等待0到10秒
     */
    private static void sleepMethod(int index){
        try {
            long sleepTime = new Double(Math.random() * 10000).longValue();
            Thread.sleep(sleepTime);
            System.out.println("目前線程執行結束: " + index);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 方法一:isTerminated
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            int index = i;
            pool.execute(() -> sleepMethod(index));
        }
        pool.shutdown();
        while (!pool.isTerminated()){
            Thread.sleep(1000);
            System.out.println("還沒停止。。。");
        }
        System.out.println("全部執行完畢");
    }
}           

上述代碼處理邏輯在主線程中進行循環判斷,全部任務是否已經完成。

這裡有兩個主要方法:

  • shutdown() :對線程池進行有序關閉。調用該方法後,線程池将不再接受新的任務,但會繼續執行已送出的任務。如果線程池已經處于關閉狀态,則對該方法的調用沒有額外的作用。
  • isTerminated() :判斷線程池中的所有任務是否在關閉後完成。隻有在調用了shutdown()或shutdownNow()方法後,所有任務執行完畢,才會傳回true。需要注意的是,在調用shutdown()之前調用isTerminated()方法始終傳回false。

優缺點分析

優點 :操作簡單。

缺點 :需要關閉線程池。并且日常使用是将線程池注入到Spring容器,然後各個元件中統一用同一個線程池,不能直接關閉線程池。

方法二:getCompletedTaskCount()

測試代碼

package pool;

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

/**
 * @author 百裡
 */
public class BaiLiIsShutdownThreadPoolDemo {
    /**
     * 建立一個最大線程數15的線程池
     */
    public static ThreadPoolExecutor pool = new ThreadPoolExecutor(
            10,
            15,
            0L,
            TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(10));
    /**
     * 線程執行方法,随機等待0到10秒
     */
    private static void sleepMethod(int index){
        try {
            long sleepTime = new Double(Math.random() * 10000).longValue();
            Thread.sleep(sleepTime);
            System.out.println("目前線程執行結束: " + index);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 方法二:getCompletedTaskCount
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            int index = i;
            pool.execute(() -> sleepMethod(index));
        }
        //當線程池完成的線程數等于線程池中的總線程數
        while (!(pool.getTaskCount() == pool.getCompletedTaskCount())) {
            System.out.println("任務總數:" + pool.getTaskCount() + "; 已經完成任務數:" + pool.getCompletedTaskCount());
            Thread.sleep(1000);
            System.out.println("還沒停止。。。");
        }
        System.out.println("全部執行完畢");
    }
}           

上述代碼處理邏輯還是一樣在主線程循環判斷,主要就兩個方法:

  • getTaskCount() :傳回計劃執行的任務總數。由于任務和線程的狀态可能在計算過程中動态變化,傳回的值隻是一個近似值。這個方法傳回的是線程池送出的任務總數,包括已經完成和正在執行中的任務。
  • getCompletedTaskCount() :傳回已經完成執行的任務的大緻總數。由于任務和線程的狀态可能在計算過程中動态改變,傳回的值隻是一個近似值,并且在連續的調用中不會減少。這個方法傳回的是已經完成執行的任務數量,不包括正在執行中的任務。

優缺點分析

  • 優點 :不必關閉線程池,避免了建立和銷毀帶來的損耗。
  • 缺點 :使用這種判斷存在很大的限制條件;必須确定在循環判斷過程中沒有新的任務産生。

方法三:CountDownLatch

測試代碼

package pool;

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

/**
 * @author 百裡
 */
public class BaiLiIsShutdownThreadPoolDemo {
    /**
     * 建立一個最大線程數15的線程池
     */
    public static ThreadPoolExecutor pool = new ThreadPoolExecutor(
            10,
            15,
            0L,
            TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(10));
    /**
     * 線程執行方法,随機等待0到10秒
     */
    private static void sleepMethod(int index){
        try {
            long sleepTime = new Double(Math.random() * 10000).longValue();
            Thread.sleep(sleepTime);
            System.out.println("目前線程執行結束: " + index);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 方法三:CountDownLatch
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        //計數器,判斷線程是否執行結束
        CountDownLatch taskLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            int index = i;
            pool.execute(() -> {
                sleepMethod(index);
                taskLatch.countDown();
                System.out.println("目前計數器數量:" + taskLatch.getCount());
            });
        }
        //目前線程阻塞,等待計數器置為0
        taskLatch.await();
        System.out.println("全部執行完畢");
    }
}           

優缺點分析

優點 :代碼優雅,不需要對線程池進行操作。

缺點 :需要提前知道線程數量;性能較差;還需要線上程代碼塊内加上異常判斷,否則在 countDown之前發生異常而沒有處理,就會導緻主線程永遠阻塞在 await。

方法四:公共計數

測試代碼

package pool;

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

/**
 * @author 百裡
 */
public class BaiLiIsShutdownThreadPoolDemo {
    /**
     * 建立一個最大線程數15的線程池
     */
    public static ThreadPoolExecutor pool = new ThreadPoolExecutor(
            10,
            15,
            0L,
            TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(10));
    /**
     * 線程執行方法,随機等待0到10秒
     */
    private static void sleepMethod(int index){
        try {
            long sleepTime = new Double(Math.random() * 10000).longValue();
            Thread.sleep(sleepTime);
            System.out.println("目前線程執行結束: " + index);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static int taskNum = 0; //計數器

    /**
     * 方法四:公共計數
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        Lock lock = new ReentrantLock();
        for (int i = 0; i < 10; i++) {
            int index = i;
            pool.execute(() -> {
                sleepMethod(index);
                lock.lock();
                taskNum++;
                lock.unlock();
            });
        }
        while(taskNum < 10) {
            Thread.sleep(1000);
            System.out.println("還沒停止。。。目前完成任務數:" + taskNum);
        }
        System.out.println("全部執行完畢");
    }
}           

這種實作其實就是通過加鎖計數,然後循環判斷。

優缺點分析

  • 優點 :手動維護方式更加靈活,對于一些特殊場景可以手動處理。
  • 缺點 :和CountDownLatch相比,一樣需要知道線程數目,但是代碼實作比較麻煩。

方法五:Future

測試代碼

package pool;

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

/**
 * @author 百裡
 */
public class BaiLiIsShutdownThreadPoolDemo {
    /**
     * 建立一個最大線程數15的線程池
     */
    public static ThreadPoolExecutor pool = new ThreadPoolExecutor(
            10,
            15,
            0L,
            TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(10));
    /**
     * 線程執行方法,随機等待0到10秒
     */
    private static void sleepMethod(int index){
        try {
            long sleepTime = new Double(Math.random() * 10000).longValue();
            Thread.sleep(sleepTime);
            System.out.println("目前線程執行結束: " + index);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 方法五:Future
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        Future future = pool.submit(() -> sleepMethod(1));
        while (!future.isDone()){
            Thread.sleep(1000);
            System.out.println("還沒停止。。。");
        }
        System.out.println("全部執行完畢");
    }
}           

優缺點分析

優點:使用簡單,不需要關閉線程池。

缺點:每個送出給線程池的任務都會關聯一個Future對象,這可能會引入額外的記憶體開銷。如果需要處理大量的任務,可能會占用較多的記憶體。

測試代碼彙總

package pool;

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

/**
 * 五種判斷線程池任務執行完成的方式
 * @author 百裡
 */
public class BaiLiIsShutdownThreadPoolDemo {
    /**
     * 建立一個最大線程數15的線程池
     */
    public static ThreadPoolExecutor pool = new ThreadPoolExecutor(
            10,
            15,
            0L,
            TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(10));
    /**
     * 線程執行方法,随機等待0到10秒
     */
    private static void sleepMethod(int index){
        try {
            long sleepTime = new Double(Math.random() * 10000).longValue();
            Thread.sleep(sleepTime);
            System.out.println("目前線程執行結束: " + index);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 方法一:isTerminated
     * @param args
     * @throws InterruptedException
     */
    public static void isTerminatedTest(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            int index = i;
            pool.execute(() -> sleepMethod(index));
        }
        pool.shutdown();
        while (!pool.isTerminated()){
            Thread.sleep(1000);
            System.out.println("還沒停止。。。");
        }
        System.out.println("全部執行完畢");
    }


    /**
     * 方法二:getCompletedTaskCount
     * @param args
     * @throws InterruptedException
     */
    public static void completedTaskCountTest(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            int index = i;
            pool.execute(() -> sleepMethod(index));
        }
        //當線程池完成的線程數等于線程池中的總線程數
        while (!(pool.getTaskCount() == pool.getCompletedTaskCount())) {
            System.out.println("任務總數:" + pool.getTaskCount() + "; 已經完成任務數:" + pool.getCompletedTaskCount());
            Thread.sleep(1000);
            System.out.println("還沒停止。。。");
        }
        System.out.println("全部執行完畢");
    }

    /**
     * 方法三:CountDownLatch
     * @throws Exception
     */
    public static void countDownLatchTest(String[] args) throws Exception {
        //計數器,判斷線程是否執行結束
        CountDownLatch taskLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            int index = i;
            pool.execute(() -> {
                sleepMethod(index);
                taskLatch.countDown();
                System.out.println("目前計數器數量:" + taskLatch.getCount());
            });
        }
        //目前線程阻塞,等待計數器置為0
        taskLatch.await();
        System.out.println("全部執行完畢");
    }

    private static int taskNum = 0;

    /**
     * 方法四:公共計數
     * @throws Exception
     */
    public static void countTest(String[] args) throws Exception {
        Lock lock = new ReentrantLock();
        for (int i = 0; i < 10; i++) {
            int index = i;
            pool.execute(() -> {
                sleepMethod(index);
                lock.lock();
                taskNum++;
                lock.unlock();
            });
        }
        while(taskNum < 10) {
            Thread.sleep(1000);
            System.out.println("還沒停止。。。目前完成任務數:" + taskNum);
        }
        System.out.println("全部執行完畢");
    }

    /**
     * 方法五:Future
     * @throws Exception
     */
    public static void futureTest(String[] args) throws Exception {
        Future future = pool.submit(() -> sleepMethod(1));
        while (!future.isDone()){
            Thread.sleep(1000);
            System.out.println("還沒停止。。。");
        }
        System.out.println("全部執行完畢");
    }
}           
5種判斷線程池任務執行完成的方式

繼續閱讀