您好,登錄后才能下訂單哦!
如何在Java項目中利用線程池執行多個任務?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
Java主要應用于:1. web開發;2. Android開發;3. 客戶端開發;4. 網頁開發;5. 企業級應用開發;6. Java大數據開發;7.游戲開發等。
在使用線程池執行任務之前,我們弄清楚什么任務可以被線程池調用。按照任務是否有返回值可以將任務分為兩種,分別是實現Runnable的任務類(無參數無返回值)和實現Callable接口的任務類(無參數有返回值)。在打代碼時根據需求選擇對應的任務類型。
多線程任務類型,首先自然想到的就是實現 Runnable 接口的類,Runnable接口提供了一個抽象方法run,這個方法無參數,無返回值。例如:
Runnable task = new Runnable() { @Override public void run() { System.out.println("Execute task."); } };
或者Java 8 及以上版本更簡單的寫法:
Runnable task = ()->{ System.out.println("Execute task."); };
于Runnable一樣Callable也只有一個抽象方法,不過該抽象方法有返回值。在實現該接口的時候需要制定返回值的類型。例如:
Callable<String> callableTask = ()-> "finished";
java.util.concurrent.Executors 提供了一系列靜態方法來創建各種線程池。下面例舉出了主要的一些線程池及特性,其它未例舉線程池的特性可由下面這些推導出來。
顧名思義,這種類型線程池線程數量是固定的。如果線程數量設置為n,則任何時刻該線程池最多只有n個線程處于運行狀態。當線程池中處于飽和運行狀態時,再往線程池中提交的任務會被放到執行隊列中。如果線程池處于不飽和狀態,線程池也會一直存在,直到ExecuteService 的shutdown方法被調用,線程池才會被清除。
// 創建線程數量為5的線程池。 ExecutorService executorService = Executors.newFixedThreadPool(5);
這種類型的線程池初始大小為0個線程,隨著往池里不斷提交任務,如果線程池里面沒有閑置線程(0個線程也表示沒有閑置線程),則會創建新的線程,保證沒有任務在等待;如果有閑置線程,則復用閑置狀態線程執行任務。處于閑置狀態的線程只會在線程池中緩存60秒,閑置時間達到60s的線程會被關閉并移出線程池。在處理大量短暫的(官方說法:short-lived)異步任務時可以顯著得提供程序性能。
//創建一個可緩存的線程池 ExecutorService executorService = Executors.newCachedThreadPool();
這或許不能叫線程池了,由于它里面的線程永遠只有1個,而且自始至終都只有1個(為什么說這句話,因為要和 Executors.newFixedThreadPool(1) 區別開來),所以還是叫它“單線程池把”。你盡可以往單線程池中添加任務,但是每次只執行1個,且任務是按順序執行的。如果前面的任務出現了異常,當前線程會被銷毀,但1個新的線程會被創建用來執行后面的任務。以上這些和線程數只有1個的線程Fixed Thread Pool一樣。兩者唯一不同的是, Executors.newFixedThreadPool(1) 可以在運行時修改它里面的線程數,而 Executors.newSingleThreadExecutor() 永遠只能有1個線程。
//創建一個單線程池 ExecutorService executorService = Executors.newSingleThreadExecutor();
扒開源碼,會發現工作竊取線程池本質是 ForkJoinPool ,這類線程池充分利用CPU多核處理任務,適合處理消耗CPU資源多的任務。它的線程數不固定,維護的任務隊列有多個,當一個任務隊列完成時,相應的線程會從其它的任務隊列中竊取任務執行,這也意味著任務的開始執行順序并和提交順序相同。如果有更高的需求,可以直接通過ForkJoinPool獲取線程池。
//創建一個工作竊取線程池,使用CPU核數等于機器的CPU核數 ExecutorService executorService = Executors.newWorkStealingPool(); //創建一個工作竊取線程池,使用CPU 3 個核進行計算,工作竊取線程池不能設置線程數 ExecutorService executorService2 = Executors.newWorkStealingPool(3);
計劃任務線程池可以按計劃執行某些任務,例如:周期性的執行某項任務。
// 獲取一個大小為2的計劃任務線程池 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2); // 添加一個打印當前線程信息計劃任務,該任務在3秒后執行 scheduledExecutorService.schedule(() -> { System.out.println(Thread.currentThread()); }, 3, TimeUnit.SECONDS); // 添加一個打印當前線程信息計劃任務,該任務在2秒后首次執行,之后每5秒執行一次。如果任務執行時間超過了5秒,則下一次將會在前一次執行完成之后立即執行 scheduledExecutorService.scheduleAtFixedRate(() -> { System.out.println(Thread.currentThread()); }, 2, 5, TimeUnit.SECONDS); // 添加一個打印當前線程信息計劃任務,該任務在2秒后首次執行,之后每次在任務執行之后5秒執行下一次。 scheduledExecutorService.scheduleWithFixedDelay(() -> { System.out.println(Thread.currentThread()); }, 2, 5, TimeUnit.SECONDS); // 逐個清除 idle 狀態的線程 scheduledExecutorService.shutdown(); // 阻塞,在線程池被關調之前代碼不再往下走 scheduledExecutorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
前面提到,任務類型分為有返回值和無返回值的類型,這里的調用也分為有返回值調用和無返回值的調用。
如果是無返回值任務的調用,可以用execute或者submit方法,這種情況下二者本質上一樣。為了于有返回值任務調用保持統一,建議采用submit方法。
//創建一個線程池 ExecutorService executorService = Executors.newFixedThreadPool(3); //提交一個無返回值的任務(實現了Runnable接口) executorService.submit(()->System.out.println("Hello")); executorService.shutdown(); executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
如果有一個任務集合,可以一個個提交。
//創建一個線程池 ExecutorService executorService = Executors.newFixedThreadPool(3); List<Runnable> tasks = Arrays.asList( ()->System.out.println("Hello"), ()->System.out.println("World")); //逐個提交任務 tasks.forEach(executorService::submit); executorService.shutdown(); executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
有返回值的任務需要實現Callable接口,實現的時候在泛型位置指定返回值類型。在調用submit方法時會返回一個Future對象,通過Future的方法get()可以拿到返回值。這里需要注意的是,調用get()時代碼會阻塞,直到任務完成,有返回值。
ExecutorService executorService = Executors.newFixedThreadPool(2); Future<String> future = executorService.submit(()->"Hello"); System.out.println(future.isDone());//false String value = future.get(); System.out.println(future.isDone());//true System.out.println(value);//Hello
如果要提交一批任務,ExecutorService除了可以逐個提交之外,還可以調用invokeAll一次性提交,invokeAll的內部實現其實就是用一個循環逐個提交任務。invokeAll返回的值是一個Future List。
ExecutorService executorService = Executors.newFixedThreadPool(2); List<Callable<String>> tasks = Arrays.asList(()->"Hello", ()->"World"); List<Future<String>> futures = executorService.invokeAll(tasks);
invokeAny方法也很有用,線程池執行若干個實現了Callable的任務,然后返回最先執行結束的任務的值,其它未完成的任務將被正常取消掉不會有異常。如下代碼不會輸出“Hello”
ExecutorService executorService = Executors.newFixedThreadPool(2); List<Callable<String>> tasks = Arrays.asList( () -> { Thread.sleep(500L); System.out.println("Hello"); return "Hello"; }, () -> { System.out.println("World"); return "World"; }); String s = executorService.invokeAny(tasks); System.out.println(s);//World
輸出:
World World
另外,在查看ExecutorService源碼時發現它還提供了一個方法 <T> Future<T> submit(Runnable task, T result);
,可以通過這個方法提交一個實現了Runnable接口的任務,然后有返回值,而Runnable接口中的run方法時沒有返回值的。那它的返回值是哪來的呢?其實問題在于該submit方法后面的一個參數,這個參數值就是返回的值。調用submit方法之后,有一通操作,然后直接把result參數返回了。
ExecutorService executorService = Executors.newFixedThreadPool(1); Future<String> future = executorService.submit(() -> System.out.println("Hello"), "World"); System.out.println(future.get());//輸出:World
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。