【Java】Java多線程任務超時結束的5種實現方法


方法一:使用Thread.join(long million)

(先講一下本人對join方法的理解,已理解此方法的可以略過)join方法可以這樣理解,在理解它之前,先解釋另一個常識,即當前線程(后面稱為目標線程,因為它是我們想使其超時結束的目標任務)的創建及start的調用,一定是在另一個線程中進行的(最起碼是main線程,也可以是不同於main線程的其他線程),這里我們假設為main線程,並且稱之為依賴線程,因為目標線程的創建是在他里面執行的。介紹完這些常識就可以進一步解釋了,join的字面意思是,使目標線程加入到依賴線程中去,也可以理解為在依賴線程中等待目標線程一直執行直至結束(如果沒有設置超時參數的話)。設置了超時參數(假設為5秒)就會這樣執行,在依賴線程中調用了join之后,相當於告訴依賴線程,現在我要插入到你的線程中來,即兩個線程合二為一,相當於一個線程(如果不執行插入的話,那目標線程和依賴線程就是並行執行),而且目標線程是插在主線程前面,所以目標線程先執行,但你主線程只需要等我5秒,5秒之后,不管我有沒有執行完畢,我們兩都分開,這時又會變成兩個並行執行的線程,而不是目標線程直接結束執行,這點很重要。

其實這個方法比較牽強,因為它主要作用是用來多個線程之間進行同步的。但因為它提供了這個帶參數的方法(所以這也給了我們一個更廣泛的思路,就是一般帶有超時參數的方法我們都可以嘗試着用它來實現超時結束任務),所以我們可以用它來實現。注意這里的參數的單位是固定的毫秒,不同於接下來的帶單位的函數。具體用法請看示例:

public class JoinTest {
    public static void main(String[] args) {
        Task task1 = new Task("one", 4);
        Task task2 = new Task("two", 2);
        Thread t1 = new Thread(task1);
        Thread t2 = new Thread(task2);
        t1.start();
        try {
            t1.join(2000); // 在主線程中等待t1執行2秒
        } catch (InterruptedException e) {
            System.out.println("t1 interrupted when waiting join");
            e.printStackTrace();
        }
        t1.interrupt(); // 這里很重要,一定要打斷t1,因為它已經執行了2秒。
        t2.start();
        try {
            t2.join(1000);
        } catch (InterruptedException e) {
            System.out.println("t2 interrupted when waiting join");
            e.printStackTrace();
        }
    }
}

class Task implements Runnable {
    public String name;
    private int time;

    public Task(String s, int t) {
        name = s;
        time = t;
    }

    public void run() {
        for (int i = 0; i < time; ++i) {
            System.out.println("task " + name + " " + (i + 1) + " round");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println(name
                        + "is interrupted when calculating, will stop...");
                return; // 注意這里如果不return的話,線程還會繼續執行,所以任務超時后在這里處理結果然后返回
            }
        }
    }
}

在主線程中等待t1執行2秒之后,要interrupt(而不是直接調用stop,這個方法已經被棄用)掉它,然后在t1里面會產出一個中斷異常,在異常里面處理完該處理的事,就要return,一定要return,如果不return的話,t1還會繼續執行,只不過是與主線程並行執行。

方法二:Future.get(long million, TimeUnit unit) 配合Future.cancle(true)

Future系列(它的子類)的都可以實現,這里采用最簡單的Future接口實現。

public class FutureTest {
    static class Task implements Callable<Boolean> {
        public String name;
        private int time;

        public Task(String s, int t) {
            name = s;
            time = t;
        }

        @Override
        public Boolean call() throws Exception {
            for (int i = 0; i < time; ++i) {
                System.out.println("task " + name + " round " + (i + 1));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println(name
                            + " is interrupted when calculating, will stop...");
                    return false; // 注意這里如果不return的話,線程還會繼續執行,所以任務超時后在這里處理結果然后返回
                }
            }
            return true;
        }
    }

    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task1 = new Task("one", 5);
        Future<Boolean> f1 = executor.submit(task1);
        try {
            if (f1.get(2, TimeUnit.SECONDS)) { // future將在2秒之后取結果
                System.out.println("one complete successfully");
            }
        } catch (InterruptedException e) {
            System.out.println("future在睡着時被打斷");
            executor.shutdownNow();
        } catch (ExecutionException e) {
            System.out.println("future在嘗試取得任務結果時出錯");
            executor.shutdownNow();
        } catch (TimeoutException e) {
            System.out.println("future時間超時");
            f1.cancel(true);
            // executor.shutdownNow();
            // executor.shutdown();
        } finally {
            executor.shutdownNow();
        }
    }
}

運行結果如下,task在2秒之后停止:

如果把Task中捕獲InterruptedException的catch塊中的return注釋掉,就是這樣的結果:

task繼續執行,直至結束

方法三:ExecutorService.awaitTermination(long million, TimeUnit unit)

這個方法會一直等待所有的任務都結束,或者超時時間到立即返回,若所有任務都完成則返回true,否則返回false

public class AwaitTermination {
    static class Task implements Runnable {
        public String name;
        private int time;

        public Task(String s, int t) {
            name = s;
            time = t;
        }

        public void run() {
            for (int i = 0; i < time; ++i) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println(name
                            + " is interrupted when calculating, will stop...");
                    return; // 注意這里如果不return的話,線程還會繼續執行,所以任務超時后在這里處理結果然后返回
                }
                System.out.println("task " + name + " " + (i + 1) + " round");
            }
            System.out.println("task " + name + " finished successfully");
        }
    }

    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task("one", 5);
        Task task2 = new Task("two", 2);
        Future<?> future = executor.submit(task);
        Future<?> future2 = executor.submit(task2);
        List<Future<?>> futures = new ArrayList<Future<?>>();
        futures.add(future);
        futures.add(future2);
        try {
            if (executor.awaitTermination(3, TimeUnit.SECONDS)) {
                System.out.println("task finished");
            } else {
                System.out.println("task time out,will terminate");
                for (Future<?> f : futures) {
                    if (!f.isDone()) {
                        f.cancel(true);
                    }
                }
            }
        } catch (InterruptedException e) {
            System.out.println("executor is interrupted");
        } finally {
            executor.shutdown();
        }
    }
}

運行結果如下:

方法四:設置一個守護線程,守護線程先sleep一段定時時間,睡醒后打斷它所監視的線程

public class DemonThread {
    static class Task implements Runnable {
        private String name;
        private int time;

        public Task(String s, int t) {
            name = s;
            time = t;
        }

        public int getTime() {
            return time;
        }

        public void run() {
            for (int i = 0; i < time; ++i) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println(name
                            + " is interrupted when calculating, will stop...");
                    return; // 注意這里如果不return的話,線程還會繼續執行,所以任務超時后在這里處理結果然后返回
                }
                System.out.println("task " + name + " " + (i + 1) + " round");
            }
            System.out.println("task " + name + " finished successfully");
        }
    }

    static class Daemon implements Runnable {
        List<Runnable> tasks = new ArrayList<Runnable>();
        private Thread thread;
        private int time;

        public Daemon(Thread r, int t) {
            thread = r;
            time = t;
        }

        public void addTask(Runnable r) {
            tasks.add(r);
        }

        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(time * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                thread.interrupt();
            }
        }

    }

    public static void main(String[] args) {
        Task task1 = new Task("one", 5);
        Thread t1 = new Thread(task1);
        Daemon daemon = new Daemon(t1, 3);
        Thread daemoThread = new Thread(daemon);
        daemoThread.setDaemon(true);
        t1.start();
        daemoThread.start();
    }
}

一開始准備在守護任務里面用一個集合來實現監視多個任務,接着發現要實現這個功能還得在這個守護任務里面為每一個監視的任務開啟一個監視任務,一時又想不到更好的方法來解決,索性只監視一個算了,留待以后改進吧。
運行結果如下:

方法五:使用Timer / TimerTask,或其他schedule定時相關的類
總結:需要注意的是,無論以上哪一種方法,其實現原理都是在超時后通過interrupt打斷目標線程的運行,所以都要在捕捉到InterruptedException的catch代碼塊中return,否則線程仍然會繼續執行。另外,最后兩種方法本質上是一樣的,都是通過持有目標線程的引用,在定時結束后打斷目標線程,這兩種方法的控制精度最低,因為它是采用另一個線程來監視目標線程的運行時間,因為線程調度的不確定性,另一個線程在定時結束后不一定會馬上得到執行而打斷目標線程。

 

原文:https://blog.csdn.net/wonking666/article/details/76552019


免責聲明!

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



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