Java線程池介紹

根據摩爾定律(Moore’s law),集成電路晶體管的數量差不多每兩年就會翻一倍。但是晶體管數量指數級的增長不一定會導致 CPU 性能的指數級增長。處理器制造商花了很多年來提高時鐘頻率和指令并行。在新一代的處理器上,單線程程序的執行速率確實有所提高。但是,時鐘頻率不可能無限制地提高,如處理器 AMD FX-9590 的時鐘頻率達到5 GHz,這已經非常困難了。如今處理器制造商更喜歡采用多核處理器(multi-core processors)。擁有4核的智能手機已經非常普遍,更不用提手提電腦和臺式機。結果,軟件不得不采用多線程的方式,以便能夠更好的使用硬件。線程池可以幫助程序員更好地利用多核 CPU。

?

線程池

?

好的軟件設計不建議手動創建和銷毀線程。線程的創建和銷毀是非常耗 CPU 和內存的,因為這需要 JVM 和操作系統的參與。64位 JVM 默認線程棧是大小1 MB。這就是為什么說在請求頻繁時為每個小的請求創建線程是一種資源的浪費。線程池可以根據創建時選擇的策略自動處理線程的生命周期。重點在于:在資源(如內存、CPU)充足的情況下,線程池沒有明顯的優勢,否則沒有線程池將導致服務器奔潰。有很多的理由可以解釋為什么沒有更多的資源。例如,在拒絕服務(denial-of-service)攻擊時會引起的許多線程并行執行,從而導致線程饑餓(thread starvation)。除此之外,手動執行線程時,可能會因為異常導致線程死亡,程序員必須記得處理這種異常情況。

?

即使在你的應用中沒有顯式地使用線程池,但是像 Tomcat、Undertow這樣的web服務器,都大量使用了線程池。所以了解線程池是如何工作的,怎樣調整,對系統性能優化非常有幫助。

?

線程池可以很容易地通過 Executors 工廠方法來創建。JDK 中實現 ExecutorService 的類有:

?

  • ForkJoinPool

  • ThreadPoolExecutor

  • ScheduledThreadPoolExecutor

?

這些類都實現了線程池的抽象。下面的一小段代碼展示了 ExecutorService 的生命周期:

?

 1   public List<Future<T>> executeTasks(Collection<Callable<T>> tasks) {
 2 
 3         // create an ExecutorService
 4         // 創建 ExecutorService
 5         final ExecutorService executorService = Executors.newSingleThreadExecutor();
 6 
 7         // execute all tasks
 8         // 執行所有任務
 9         final List<Future<T>> executedTasks = executorService.invokeAll(tasks);
10 
11         // shutdown the ExecutorService after all tasks have completed
12         // 所有任務執行完后關閉 ExecutorService
13         executorService.shutdown();
14 
15         return executedTasks;
16 
17     }

?

?

首先,創建一個最簡單的 ExecutorService —— 一個單線程的執行器(executor)。它用一個線程來處理所有的任務。當然,你也可以通過各種方式自定義 ExecutorService,或者使用 Executors 類的工程方法來創建 ExecutorService:

?

newCachedThreadPool() :創建一個 ExecutorService,該 ExecutorService 根據需要來創建線程,可以重復利用已存在的線程來執行任務。

?

newFixedThreadPool(int numberOfThreads) :創建一個可重復使用的、固定線程數量的 ExecutorService。

?

newScheduledThreadPool(int corePoolSize):根據時間計劃,延遲給定時間后創建 ExecutorService(或者周期性地創建 ExecutorService)。

?

newSingleThreadExecutor():創建單個工作線程 ExecutorService。

?

newSingleThreadScheduledExecutor():根據時間計劃延遲創建單個工作線程 ExecutorService(或者周期性的創建)。

?

newWorkStealingPool():創建一個擁有多個任務隊列(以便減少連接數)的 ExecutorService。

?

在上面這個例子里,所有的任務都只執行一次,你也可以使用其他方法來執行任務:

?

  • void execute(Runnable)

  • Future submit(Callable)

  • Future submit(Runnable)

?

最后,關閉 executorService。Shutdown() 是一個非阻塞式方法。調用該方法后,ExecutorService 進入“關閉模式(shutdown mode)”,在該模式下,之前提交的任務都會執行完成,但是不會接收新的任務。如果想要等待任務執行完成,需要調用 awaitTermination() 方法。

?

ExecutorService 是一個非常有用的工具,可以幫助我們很方便執行所有的任務。它的好處在什么地方呢?我們不需要手動創建工作線程。一個工作線程就是 ExecutorService 內部使用的線程。值得注意的是,ExecutorService 管理線程的生命周期。它可以在負載增加的時候增加工作線程。另一方面,在一定周期內,它也可以減少空閑的線程。當我們使用線程池的時候,我們就不再需要考慮線程本身。我們只需要考慮異步處理的任務。此外,當出現不可預期的異常時,我們不再需要重復創建線程,我們也不需要擔心當一個線程執行完任務后的重復使用問題。最后,一個任務提交以后,我們可以獲取一個未來結果的抽象——Future。當然,在 Java 8中,我們可以使用更優秀的 CompletableFuture,如何將一個 Future 轉換為 CompletableFuture 已超出了本文所討論的范圍。但是請記住,只有提交的任務是一個 Callable 時,Future 才有意義,因為 Callable 有輸出結果,而 Runnable 沒有。

?

內部組成

?

每個線程池由幾個模塊組成:

?

  • 一個任務隊列,

  • 一個工作線程的集合,

  • 一個線程工廠,

  • 管理線程狀態的元數據。

?

ExecutorService 接口有很多實現,我們重點關注一下最常用的 ThreadPoolExecutor。實際上,newCachedThreadPool()、newFixedThreadPool() 和 newSingleThreadExecutor() 三個方法返回的都是 ThreadPoolExecutor 類的實例。如果要手動創建一個ThreadPoolExecutor 類的實例,至少需要5個參數:

?

  • int corePoolSize:線程池保存的線程數量。

  • int maximumPoolSize:線程的最大數量。

  • long keepAlive and TimeUnit unit:超出 corePoolSize 大小后,線程空閑的時間到達給定時間后將會關閉。

  • BlockingQueue workQueue:提交的任務將被放置在該隊列中等待執行。

  • thread-pool

?

?

阻塞隊列

?

LinkedBlockingQueue 是調用 Executors 類中的方法生成 ThreadPoolExecutor 實例時使用的默認隊列,PriorityBlockingQueue 實際上也是一個BlockingQueue,不過,根據設定的優先級來處理任務也是一個棘手的問題。首先,提交一個 Runnable 或 Callable 任務,該任務被包裝成一個 RunnableFuture,然后添加到隊列中,ProrityBlockingQueue 比較每個對象來決定執行的優先權(比較對象是包裝后的RunnableFuture而不是任務的內容)。不僅如此,當 corePoolSize 大于1并且工作線程空閑時,ThreadPoolExecutor 可能會根據插入順序來執行,而不是 PriorityBlockingQueue 所期望的優先級順序。

?

默認情況下,ThreadPoolExecutor 的工作隊列(workQueue)是沒有邊界的。通常這是沒問題的,但是請記住,沒有邊界的工作隊列可能導致應用出現內存溢出(out of memory)錯誤。如果要限制任務隊列的大小,可以設置 RejectionExecutionHandler。你可以自定義處理器或者從4個已有處理器(默認AbortPolicy)中選擇一個:

?

  • CallerRunsPolicy

  • AbortPolicy

  • DiscardPolicy

  • DiscardOldestPolicy

?

線程工廠

?

線程工廠通常用于創建自定義的線程。例如,你可以增加自定義的 Thread.UncaughtExceptionHandler 或者設置線程名稱。在下面的例子中,使用線程名稱和線程的序號來記錄未捕獲的異常。

?

 1 public class LoggingThreadFactory implements ThreadFactory {
 2 
 3     private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 4     private static final String THREAD_NAME_PREFIX = "worker-thread-";
 5     private final AtomicInteger threadCreationCounter = new AtomicInteger();
 6 
 7     @Override
 8     public Thread newThread(Runnable task) {
 9 
10         int threadNumber = threadCreationCounter.incrementAndGet();
11         Thread workerThread = new Thread(task, THREAD_NAME_PREFIX + threadNumber);
12     
13         workerThread.setUncaughtExceptionHandler(thread, throwable -> logger.error("Thread {} {}", thread.getName(), throwable));
14     
15         return workerThread;
16 
17     }
18 }

?

?

生產者消費者實例

?

生產者消費者是一種常見的同步多線程處理問題。在這個例子中,我們使用 ExecutorService 解決此問題。但是,這不是解決該問題的教科書例子。我們的目標是演示線程池來處理所有的同步問題,從而程序員可以集中精力去實現業務邏輯。

?

Producer 定期的從數據庫獲取新的數據來創建任務,并將任務提交給 ExecutorService。ExecutorService 管理的線程池中的一個工作線程代表一個 Consumer,用于處理業務任務(如計算價格并返回給客戶)。

?

首先,我們使用 Spring 來配置:

?

 1 @Configuration
 2 public class ProducerConsumerConfiguration {
 3 
 4     @Bean
 5     public ExecutorService executorService() {
 6 
 7         // single consumer
 8         return Executors.newSingleThreadExecutor();
 9     }
10 
11     // other beans such as a data source, a scheduler, etc.
12 
13 }

?

?

然后,建立一個 Consumer 及一個 ConsumerFactory。該工程方法通過生產者調用來創建一個任務,在未來的某一個時間點,會有一個工作線程執行該任務。

?

 1 public class Consumer implements Runnable {
 2 
 3     private final BusinessTask businessTask;
 4     private final BusinessLogic businessLogic;
 5 
 6     public Consumer(BusinessTask businessTask, BusinessLogic businessLogic) {
 7 
 8         this.businessTask = businessTask;
 9         this.businessLogic = businessLogic;
10 
11     }
12 
13     @Override
14     public void run() {
15 
16         businessLogic.processTask(businessTask);
17     }
18 
19 }
20 
21 @Component
22 public class ConsumerFactory {
23 
24     private final BusinessLogic businessLogic;
25 
26     public ConsumerFactory(BusinessLogic businessLogic) {
27         this.businessLogic = businessLogic;
28     }
29 
30     public Consumer newConsumer(BusinessTask businessTask) {
31         return new Consumer(businessTask, businessLogic);
32     }
33 
34 }

?

?

最后,有一個 Producer 類,用于從數據庫中獲取數據并創建業務任務。在這個例子中,我們假定 fetchData() 是通過 scheduler 周期性調用的。

?

 1 @Component
 2 public class Producer {
 3 
 4     private final DataRepository dataRepository;
 5     private final ExecutorService executorService;
 6     private final ConsumerFactory consumerFactory;
 7 
 8     @Autowired
 9     public Producer(DataRepository dataRepository, ExecutorService executorService,
10 
11     ConsumerFactory consumerFactory) {
12 
13         this.dataRepository = dataRepository;
14         this.executorService = executorService;
15         this.consumerFactory = consumerFactory;
16 
17     }
18 
19     public void fetchAndSubmitForProcessing() {
20 
21         List<Data> data = dataRepository.fetchNew();
22     
23         data.stream()
24         // create a business task from data fetched from the database
25         .map(BusinessTask::fromData)
26         // create a consumer for each business task
27         .map(consumerFactory::newConsumer)
28         // submit the task for further processing in the future (submit is a non-blocking method)
29         .forEach(executorService::submit);
30 
31     }
32 }

?

?

非常感謝 ExecutorService,這樣我們就可以集中精力實現業務邏輯,我們不需要擔心同步問題。上面的演示代碼只用了一個生產者和一個消費者。但是,很容易擴展為多個生產者和多個消費者的情況。

?

總結

?

JDK 5 誕生于2004年,提供很多有用的并發工具,ExecutorService 類就是其中的一個。線程池通常應用于服務器的底層(如 Tomcat 和 Undertow)。當然,線程池也不僅僅局限于服務器環境。在任何密集并行(embarrassingly parallel)難題中它們都非常有用。由于現在越來越多的軟件運行于多核系統上,線程池就更值得關注了。

轉載于:https://www.cnblogs.com/kangye1014/p/4993961.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/458636.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/458636.shtml
英文地址,請注明出處:http://en.pswp.cn/news/458636.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

curl -L get.rvm.io | bash -s stable報錯:連接不上服務器

1、安裝cocoa pods時&#xff0c; ERROR: Error installing cocoa: activesupport requires Ruby version > 2.2.2. 這個錯誤是說&#xff1a;rvm的版本過低&#xff0c;需要升級一下版本 2、升級rvm版本的時候&#xff0c;報標題的錯誤解決辦法如下 將上面的命令行改成&a…

C語言中#define的用法(轉)

轉自&#xff1a;http://www.dingge.com/main/article.asp?id10 今天整理了一些#define的用法&#xff0c;與大家共享&#xff01; 1.簡單的define定義 #define MAXTIME 1000 一個簡單的MAXTIME就定義好了&#xff0c;它代表1000&#xff0c;如果在程序里面寫 if(i<MAXTIM…

cocoa pods的安裝與我遇到的問題

1.打開終端 終端輸入 ruby -v 查看ruby的版本 打印代碼&#xff1a; ruby 2.0.0p648 (2015-12-16 revision 53162) [universal.x86_64-darwin15] 2. 更換ruby鏡像 終端輸入如下命令&#xff08;把Ruby鏡像指向taobao&#xff0c;避免被墻&#xff0c;你懂得&#xff09; a.移…

Node 連接Mysql并進行增刪改查

NPM: NPM的全稱是Node Package Manager&#xff0c;類似于ruby的gem&#xff0c;Python的PyPL、setuptools&#xff0c;PHP的pear&#xff0c;是Nodejs中的包管理器。Nodejs自身提供了基本的模塊。但是在這些基本模塊上開發實際應用需要較多的工作。NPM上已經有近萬個Nodejs庫或…

C++/C 宏定義(define)中# ## 的含義(轉)

參考&#xff1a;http://www.cnblogs.com/little-ant/p/3463080.html http://hi.baidu.com/kiraversace/item/1148ee057147981a4ac4a3e9 C/C 宏定義&#xff08;define&#xff09;中# ## 的含義 define 中的# ## 一般是用來拼接字符串的&#xff0c;但是實際使用過程中&#x…

Implicit declaration of function 'NSFileTypeForHFSTypeCode' is invalid in C99

一般出現該問題是因為通過C調用了unix/linux 底層接口&#xff0c;所以需要調整c語言的編譯選項&#xff0c;設置方法見下圖&#xff1a;(根據實際情況選擇相應的編譯選項)

C++里的花括號{},塊,作用域

{ } 里的內容是一個“塊”。單獨的{ }在執行順序上沒有改變&#xff0c;仍然是順序執行&#xff0c;不同的是標識符的作用域限定 。#include <iostream>#include <string>using namespace std;class tcalss{string Name;public:tcalss(string name){Namename;cout&…

iOS關于armv7,armv7s,arm64,i386,x86_64等問題

iOS測試分為模擬器測試和真機測試&#xff0c;處理器分為32位處理器&#xff0c;和64位處理器&#xff0c; 模擬器32位處理器測試需要i386架構&#xff0c;&#xff08;iphone5,iphone5s以下的模擬器&#xff09; 模擬器64位處理器測試需要x86_64架構&#xff0c;(iphone6以上的…

DebugView 調試入門

參考鏈接&#xff1a;http://blog.csdn.net/jiankunking/article/details/44984487 軟件下載地址&#xff1a;點擊打開鏈接 debugview 可以捕獲程序中由TRACE(debug版本)和OutputDebugString輸出的信息。支持Debug、Release模式編譯的程序&#xff08;即該軟件捕獲的是exe直接運…

AJAX只支持字符類數據返回,不支持文件下載

如題轉載于:https://www.cnblogs.com/caicaizi/p/5000363.html

Xcode中指令集相關選項

Xcode中指令集相關選項&#xff08;Build Setting中&#xff09; &#xff08;1&#xff09;Architectures Space-separated list of identifiers. Specifies the architectures (ABIs, processor models) to which the binary is targeted. When this build setting specifies…

DebugView使用筆記

1. 什么是DebugView? 它是Sysinternals公司的系列調試工具。可以捕獲程序中由TRACE()和OutputDebugString輸出的信息。 2. C需要完成哪些工作呢&#xff1f; 將打印的信息用OutputDebugString輸出&#xff0c;示例&#xff1a; [cpp] view plaincopy #include "stdio.h&q…

DebugView的使用[通用匯總]

DebugView是一款免費輔助工具&#xff0c;用來輸出捕捉OutputDebugString()函數輸出的信息等。 1 下載 http://technet.microsoft.com/en-us/sysinternals/bb896647 1.1 介紹DebugView http://www.vcfans.com/2008/11/trace-show-about-a-tool-debugview-sent-a-message-to-tra…

Android 通信 EventBus

參考&#xff1a; Android 框架煉成 教你如何寫組件間通信框架EventBusAndroid EventBus源碼解析 帶你深入理解EventBusAndroid EventBus實戰 沒聽過你就out了快速Android開發系列通信篇之EventBusEventBus是一個publish/subscribe (發布 / 訂閱) 消息的總線&#xff0c;簡化了…

WIFI無線路由器的五種工作模式

http://www.360doc.com/content/12/0814/08/10560798_230064460.shtml

iOS面試題1

而技術開發面試&#xff0c;問一些技術相關的問題是必須的&#xff0c;最新的技術可能人人都趨之若鶩&#xff0c;但有些原理和基礎的也希望都有了解。 這里整理了一些iOS相關的面試題&#xff0c;&#xff08;可能都已經老掉牙&#xff09;但是也想跟大家分享下&#xff01; 如…

高質量JAVA代碼編寫規范

1. Java 命名約定   除了以下幾個特例之外&#xff0c;命名時應始終采用完整的英文描述符。此外&#xff0c;一般應采用小寫字母&#xff0c;但類名、接口名以及任何非初始單詞的第一個字母要大寫。 1.1 一般概念   * 盡量使用完整的英文描述符   * 采用適用于相關領域的…

MFC中繪制動態曲線

參考&#xff1a; http://blog.csdn.net/zang141588761/article/details/50536788 [轉載]MFC中畫直線和曲線 一、畫直線要想在MFC中畫出有顏色的線條&#xff0c;首先就要設置DC的畫筆&#xff0c;我們可以按如下方法來設置畫筆&#xff1a;第一步&#xff1a;在View類中添加…

博客地址 RSS地址

博客地址 RSS地址 OneVs Den http://onevcat.com/atom.xml 破船之家 http://beyondvincent.com/atom.xml NSHipster http://nshipster.cn/feed.xml Limboy 無網不剩 http://feeds.feedburner.com/lzyy 唐巧的技術博客 http://blog.devtang.com/atom.xml Lex iOS notes http://i…

2015.11.27---Java

public class star{public static void main(String[] args) {System.out.print("ha");}}轉載于:https://www.cnblogs.com/supermeimei/p/5001922.html