**java設定一段程式碼執行超時時間(轉)**

aiaishanliang發表於2019-01-21

引用網址:https://blog.csdn.net/a9529lty/article/details/42711029

前段時間在搞一個批量處理程式,涉及到多執行緒操作。但是後臺服務很不給力,併發一大常常就掛了,長時間不給返回,導致我的程式也掛死在那裡……

那麼能不能設定一段程式碼執行的超時時間呢?如果處理超時就忽略該錯誤繼續向下執行。

可是在網上搜了大半天,找到的都是無用的程式碼,根本不能用。

查了大量資料後發現,java早已經給我們提供瞭解決方案。jdk1.5自帶的併發庫中Future類就能滿足這個需求。Future類中重要方法包括get()和cancel()。get()獲取資料物件,如果資料沒有載入,就會阻塞直到取到資料,而 cancel()是取消資料載入。另外一個get(timeout)操作,表示如果在timeout時間內沒有取到就失敗返回,而不再阻塞。

到這裡,此問題就迎刃而解了。廢話不多說,直接上程式碼:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import com.sun.corba.se.impl.orbutil.closure.Future;
import com.sun.corba.se.impl.orbutil.threadpool.TimeoutException;
public class ThreadTest {

public static void main(String[] args) throws InterruptedException,  
        ExecutionException {  
      
    final ExecutorService exec = Executors.newFixedThreadPool(1);  
      
    Callable<String> call = new Callable<String>() {  
        public String call() throws Exception {  
            //開始執行耗時操作  
            Thread.sleep(1000 * 5);  
            return "執行緒執行完成.";  
        }  
    };  
      
    try {  
        Future<String> future = exec.submit(call);  
        String obj = future.get(1000 * 1, TimeUnit.MILLISECONDS); //任務處理超時時間設為 1 秒  
        System.out.println("任務成功返回:" + obj);  
    } catch (TimeoutException ex) {  
        System.out.println("處理超時啦....");  
        ex.printStackTrace();  
    } catch (Exception e) {  
        System.out.println("處理失敗.");  
        e.printStackTrace();  
    }  
    // 關閉執行緒池  
    exec.shutdown();  
}  

}
注意,以上程式碼需要 1.5 以上的 jdk 才可編譯。
執行後可以發現丟擲了 TimeoutException ,列印出了"處理超時啦…" 的文字,在此處可進行超時後的處理。

=end=華麗的分割線================

如果對執行緒、執行緒池不太瞭解,向下繼續
引用:https://www.cnblogs.com/Steven0805/p/6393443.html

Java執行緒池ExecutorService
開篇前,我們先來看看不使用執行緒池的情況:

new Thread的弊端

執行一個非同步任務你還只是如下new Thread嗎?

new Thread(new Runnable() {
 
    @Override
    public void run() {
        // TODO Auto-generated method stub
    }
}).start();

那你就太out了,new Thread的弊端如下:
a. 每次new Thread新建物件效能差。
b. 執行緒缺乏統一管理,可能無限制新建執行緒,相互之間競爭,及可能佔用過多系統資源導致當機或oom。
c. 缺乏更多功能,如定時執行、定期執行、執行緒中斷。
相比new Thread,Java提供的四種執行緒池的好處在於:
a. 重用存在的執行緒,減少物件建立、消亡的開銷,效能佳。
b. 可有效控制最大併發執行緒數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞。
c. 提供定時執行、定期執行、單執行緒、併發數控制等功能。

一 Java通過Executors提供四種執行緒池,分別為:
newCachedThreadPool建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。
newFixedThreadPool 建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。
newScheduledThreadPool 建立一個定長執行緒池,支援定時及週期性任務執行。
newSingleThreadExecutor 建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。

二、 ExecutorService 的submit() 與execute()區別
1、接收的引數不一樣 submit()可以接受runnable和callable 有返回值
execute()接受runnable 無返回值

2、submit有返回值,而execute沒有

Method submit extends base method Executor.execute by creating and returning a Future that can be used to cancel execution and/or wait for completion.

用到返回值的例子,比如說我有很多個做validation的task,我希望所有的task執行完,然後每個task告訴我它的執行結果,是成功還是失敗,如果是失敗,原因是什麼。

3、submit方便Exception處理

There is a difference when looking at exception handling. If your tasks throws an exception and if it was submitted with execute this exception will Go to the uncaught exception handler (when you don’t have provided one explicitly, the default one will just print the stack trace to System.err). If you submitted the task with submit any thrown exception, checked or not, is then part of the task’s return status. For a task that was submitted with submit and that terminates with an exception, the Future.get will rethrow this exception, wrapped in an ExecutionException.

意思就是如果你在你的task裡會丟擲checked或者unchecked exception,而你又希望外面的呼叫者能夠感知這些exception並做出及時的處理,那麼就需要用到submit,通過捕獲Future.get丟擲的異常。

import java.util.ArrayList;  
import java.util.List;  
import java.util.Random;  
import java.util.concurrent.Callable;  
import java.util.concurrent.ExecutionException;  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.Future;  

public class ExecutorServiceTest {  
    public static void main(String[] args) {  
        ExecutorService executorService = Executors.newCachedThreadPool();  
        List<Future<String>> resultList = new ArrayList<Future<String>>();  

        // 建立10個任務並執行  
        for (int i = 0; i < 10; i++) {  
            // 使用ExecutorService執行Callable型別的任務,並將結果儲存在future變數中  
            Future<String> future = executorService.submit(new TaskWithResult(i));  
            // 將任務執行結果儲存到List中  
            resultList.add(future);  
        }  
        executorService.shutdown();  

        // 遍歷任務的結果  
        for (Future<String> fs : resultList) {  
            try {  
                System.out.println(fs.get()); // 列印各個執行緒(任務)執行的結果  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            } catch (ExecutionException e) {  
                executorService.shutdownNow();  
                e.printStackTrace();  
                return;  
            }  
        }  
    }  
}  

class TaskWithResult implements Callable<String> {  
    private int id;  

    public TaskWithResult(int id) {  
        this.id = id;  
    }  

    /** 
     * 任務的具體過程,一旦任務傳給ExecutorService的submit方法,則該方法自動在一個執行緒上執行。 
     *  
     * @return 
     * @throws Exception 
     */  
    public String call() throws Exception {  
        System.out.println("call()方法被自動呼叫,幹活!!!             " + Thread.currentThread().getName());  
        if (new Random().nextBoolean())  
            throw new TaskException("Meet error in task." + Thread.currentThread().getName());  
        // 一個模擬耗時的操作  
        for (int i = 999999999; i > 0; i--)  
            ;  
        return "call()方法被自動呼叫,任務的結果是:" + id + "    " + Thread.currentThread().getName();  
    }  
}  

class TaskException extends Exception {  
    public TaskException(String message) {  
        super(message);  
    }  
}

執行的結果類似於:

call()方法被自動呼叫,幹活!!!             pool-1-thread-1 
call()方法被自動呼叫,幹活!!!             pool-1-thread-2 
call()方法被自動呼叫,幹活!!!             pool-1-thread-3 
call()方法被自動呼叫,幹活!!!             pool-1-thread-5 
call()方法被自動呼叫,幹活!!!             pool-1-thread-7 
call()方法被自動呼叫,幹活!!!             pool-1-thread-4 
call()方法被自動呼叫,幹活!!!             pool-1-thread-6 
call()方法被自動呼叫,幹活!!!             pool-1-thread-7 
call()方法被自動呼叫,幹活!!!             pool-1-thread-5 
call()方法被自動呼叫,幹活!!!             pool-1-thread-8 
call()方法被自動呼叫,任務的結果是:0    pool-1-thread-1 
call()方法被自動呼叫,任務的結果是:1    pool-1-thread-2 
java.util.concurrent.ExecutionException: com.cicc.pts.TaskException: Meet error in task.pool-1-thread-3 
    at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:222) 
    at java.util.concurrent.FutureTask.get(FutureTask.java:83) 
    at com.cicc.pts.ExecutorServiceTest.main(ExecutorServiceTest.java:29) 
Caused by: com.cicc.pts.TaskException: Meet error in task.pool-1-thread-3 
    at com.cicc.pts.TaskWithResult.call(ExecutorServiceTest.java:57) 
    at com.cicc.pts.TaskWithResult.call(ExecutorServiceTest.java:1) 
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) 
    at java.util.concurrent.FutureTask.run(FutureTask.java:138) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) 
    at java.lang.Thread.run(Thread.java:619) 

可以看見一旦某個task出錯,其它的task就停止執行。

三、shotdown() showdownNow()區別

可以關閉 ExecutorService,這將導致其拒絕新任務。提供兩個方法來關閉 ExecutorService。
shutdown() 方法在終止前允許執行以前提交的任務,
shutdownNow() 方法阻止等待任務啟動並試圖停止當前正在執行的任務。在終止時執行程式沒有任務在執行,也沒有任務在等待執行,並且無法提交新任務。關閉未使用的 ExecutorService 以允許回收其資源。
一般分兩個階段關閉 ExecutorService。第一階段呼叫 shutdown 拒絕傳入任務,然後呼叫 shutdownNow(如有必要)取消所有遺留的任務

// 啟動一次順序關閉,執行以前提交的任務,但不接受新任務。
    threadPool.shutdown();

四、Runnable()與Callable()區別

如果是一個多執行緒協作程式,比如菲波拉切數列,1,1,2,3,5,8…使用多執行緒來計算。
但後者需要前者的結果,就需要用callable介面了。
callable用法和runnable一樣,只不過呼叫的是call方法,該方法有一個泛型返回值型別,你可以任意指定。

runnable介面實現的沒有返回值的併發程式設計。
在這裡插入圖片描述
callable實現的存在返回值的併發程式設計。(call的返回值String受泛型的影響) 使用Future獲取返回值。
在這裡插入圖片描述
(1). newCachedThreadPool
建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。示例程式碼如下:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
    final int index = i;
    try {
        Thread.sleep(index * 1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
 
    cachedThreadPool.execute(new Runnable() {
 
        @Override
        public void run() {
            System.out.println(index);
        }
    });
}

執行緒池為無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的執行緒,而不用每次新建執行緒。

(2). newFixedThreadPool
建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。示例程式碼如下:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
    final int index = i;
    fixedThreadPool.execute(new Runnable() {
 
 
        @Override
        public void run() {
            try {
                System.out.println(index);
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    });
}

因為執行緒池大小為3,每個任務輸出index後sleep 2秒,所以每兩秒列印3個數字。
定長執行緒池的大小最好根據系統資源進行設定。如Runtime.getRuntime().availableProcessors()。可參考PreloadDataCache。

(3) newScheduledThreadPool
建立一個定長執行緒池,支援定時及週期性任務執行。延遲執行示例程式碼如下:

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
 
    @Override
    public void run() {
        System.out.println("delay 3 seconds");
    }
}, 3, TimeUnit.SECONDS);

表示延遲3秒執行。

定期執行示例程式碼如下:

scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
 
    @Override
    public void run() {
        System.out.println("delay 1 seconds, and excute every 3 seconds");
    }
}, 1, 3, TimeUnit.SECONDS);

表示延遲1秒後每3秒執行一次。
ScheduledExecutorService比Timer更安全,功能更強大,後面會有一篇單獨進行對比。

(4)、newSingleThreadExecutor
建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。示例程式碼如下:

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
    final int index = i;
    singleThreadExecutor.execute(new Runnable() {
 
        @Override
        public void run() {
            try {
                System.out.println(index);
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    });
}

結果依次輸出,相當於順序執行各個任務。
現行大多數GUI程式都是單執行緒的。Android中單執行緒可用於資料庫操作,檔案操作,應用批量安裝,應用批量刪除等不適合併發但可能IO阻塞性及影響UI執行緒響應的操作。

總結:

(1)使用ExecutorService的submit函式由於execute函式

(2)異常如何處理,異常後其他task停止

相關文章