線程池ExecutorService 一. new Thread的弊端
執行一個異步任務你還只是如下new Thread嗎?
new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } }).start();
那你就太out了,new Thread的弊端如下:
每次new Thread新建對象性能差.
線程缺乏統一管理,可能無限制新建線程,相互之間競爭,及可能占用過多系統資源導致死機或oom.
缺乏更多功能,如定時執行、定期執行、線程中斷.
相比new Thread,Java提供的四種線程池的好處在于:
重用存在的線程,減少對象創建、消亡的開銷,性能佳.
可有效控制最大并發線程數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞.
提供定時執行、定期執行、單線程、并發數控制等功能.
二. ExecutorService詳解Java通過Executors提供四種線程池,分別為:
newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程.
newFixedThreadPool 創建一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待.
newScheduledThreadPool 創建一個定長線程池,支持定時及周期性任務執行.
newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行.
01. submit()與execute()區別接收的參數不一樣 submit()可以接受runnable和callable 有返回值execute()接受runnable 無返回值
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告訴我它的執行結果,是成功還是失敗,如果是失敗,原因是什么.
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> resultList = new ArrayList >(); // 創建10個任務并執行 for (int i = 0; i < 10; i++) { // 使用ExecutorService執行Callable類型的任務,并將結果保存在future變量中 Future future = executorService.submit(new TaskWithResult(i)); // 將任務執行結果存儲到List中 resultList.add(future); } executorService.shutdown(); // 遍歷任務的結果 for (Future 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 { 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就停止執行.
可以關閉 ExecutorService,這將導致其拒絕新任務.提供兩個方法來關閉 ExecutorService.
shutdown() 方法在終止前允許執行以前提交的任務,
shutdownNow() 方法阻止等待任務啟動并試圖停止當前正在執行的任務.在終止時執行程序沒有任務在執行,也沒有任務在等待執行,并且無法提交新任務.關閉未使用的 ExecutorService 以允許回收其資源.
一般分兩個階段關閉 ExecutorService.第一階段調用 shutdown 拒絕傳入任務,然后調用 shutdownNow(如有必要)取消所有遺留的任務
// 啟動一次順序關閉,執行以前提交的任務,但不接受新任務.
threadPool.shutdown();
03. Runnable()與Callable()區別
如果是一個多線程協作程序,比如菲波拉切數列,1,1,2,3,5,8…使用多線程來計算.
但后者需要前者的結果,就需要用callable接口了.
callable用法和runnable一樣,只不過調用的是call方法,該方法有一個泛型返回值類型,你可以任意指定.
runnable接口實現的沒有返回值的并發編程.
callable實現的存在返回值的并發編程.(call的返回值String受泛型的影響) 使用Future獲取返回值.
這里寫圖片描述
創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程.示例代碼如下:
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); } }); }
線程池為無限大,當執行第二個任務時第一個任務已經完成,會復用執行第一個任務的線程,而不用每次新建線程.
02. 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.
創建一個定長線程池,支持定時及周期性任務執行.延遲執行示例代碼如下:
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更安全,功能更強大,后面會有一篇多帶帶進行對比.
創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(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線程響應的操作.
總結:
使用ExecutorService的submit函數由于execute函數
異常如何處理,異常后其他task停止
四. 線程安全問題 01. 日期類型轉換SimpleDateFormat來做Date到String的類型轉換,建議使用Apache commons-lang中的FastDateFormat。
因為JDK里自帶的SimpleDateFormat存在線程不安全問題。
maven依賴:
commons-lang commons-lang 2.5
代碼:
private String initDate() { Date d = new Date(); FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss"); return fdf.format(d); }
多線程問題,主要是多線程執行時的順序是隨機的,無法保證同一代碼的執行順序,任意兩步代碼(非原子)操作都存在安全問題
02. 線程安全類型多線程問題,主要是多線程執行時的順序是隨機的,無法保證同一代碼的執行順序,任意兩步代碼(非原子)操作都存在安全問題(01). 鎖在String
String str="a"; synchronized(str){ str = "b";? //str變為一個新對象,鎖失效,字符的賦值是新new一個String然后賦值的 }(02). AtomicInteger
i++,使用java.util.concurrent.atomic下的原子類代替來做多線程的計數器
i++是兩步,讀取i的變量的值,然后更新+1,所以不安全, 使用AtomicInteger
HashMap,ArrayList,使用ConcurrentHashMap,CopyOnWriteArrayList代替
(04). StringBufferStringBuilder,使用StringBuffer代替
參考引用:
https://www.cnblogs.com/Steve...
https://blog.csdn.net/insistg...
https://blog.csdn.net/c511362...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73222.html
摘要:線程池常見實現線程池一般包含三個主要部分調度器決定由哪個線程來執行任務執行任務所能夠的最大耗時等線程隊列存放并管理著一系列線程這些線程都處于阻塞狀態或休眠狀態任務隊列存放著用戶提交的需要被執行的任務一般任務的執行的即先提交的任務先被執行調度 線程池常見實現 線程池一般包含三個主要部分: 調度器: 決定由哪個線程來執行任務, 執行任務所能夠的最大耗時等 線程隊列: 存放并管理著一系列線...
摘要:并發包參考多線程的同步協助同步控制擴展功能重入鎖之前重入鎖性能好于但開始優化現在二者的性能相差不大。倒計時器的擴展循柵欄。做好異常處理工作。線程池的內部實現該部分待看書 JDK 并發包 參考:> https://github.com/chengbingh... 3.1 多線程的同步協助:同步控制 3.1.1 synchronized 擴展功能:重入鎖jdk1.5之前重入鎖Reentra...
摘要:并發編程實戰水平很高,然而并不是本好書。一是多線程的控制,二是并發同步的管理。最后,使用和來關閉線程池,停止其中的線程。當線程調用或等阻塞時,對這個線程調用會使線程醒來,并受到,且線程的中斷標記被設置。 《Java并發編程實戰》水平很高,然而并不是本好書。組織混亂、長篇大論、難以消化,中文翻譯也較死板。這里是一篇批評此書的帖子,很是貼切。俗話說:看到有這么多人罵你,我就放心了。 然而知...
摘要:本文主要內容為簡單總結中線程池的相關信息。方法簇方法簇用于創建固定線程數的線程池。三種常見線程池的對比上文總結了工具類創建常見線程池的方法,現對三種線程池區別進行比較。 概述 線程可認為是操作系統可調度的最小的程序執行序列,一般作為進程的組成部分,同一進程中多個線程可共享該進程的資源(如內存等)。在單核處理器架構下,操作系統一般使用分時的方式實現多線程;在多核處理器架構下,多個線程能夠...
摘要:創建一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。創建一個定長線程池,支持定時及周期性任務執行。 ExecutorService是Java中對線程池定義的一個接口,它java.util.concurrent包中. 創建一個什么樣的ExecutorService的實例(即線程池)需要g根據具體應用場景而定,不過Java給我們提供了一個Executors工廠類,它可以幫助...
閱讀 1830·2021-11-11 16:55
閱讀 749·2019-08-30 15:53
閱讀 3587·2019-08-30 15:45
閱讀 671·2019-08-30 14:10
閱讀 3262·2019-08-30 12:46
閱讀 2122·2019-08-29 13:15
閱讀 2025·2019-08-26 13:48
閱讀 933·2019-08-26 12:23