以往說到的線程對象都是java平台中非常初級的API,用於處理一些基本的任務,對於一些復雜高級的工作,就需要一些高級的並發對象,尤其是針對於當今的應用程序,要充分利用現在的多核多處理器系統的性能。
以下內容包括一些從java5開始java平台具有的一些高並發特性。這些特征多數在包java.util.concurrent中實現,java集合框架中也有新的並發數據結構。
同步代碼依賴於一種簡單的再進入鎖,這種鎖比較易用,但有很多局限性,java.util.concurrent.locks支持許多高級的鎖用法,在這裡集中講其中最基本的接口Lock。
Lock對象的作用方式很像同步代碼使用的隱式鎖,相似之處在於一個鎖對象一次只能被一個線程擁有,Lock對象也有wait/notify機制,盡管他們與Condition對象相關。
Lock對象相對於隱式鎖最大的優勢在於其能夠收回鎖的請求,其tryLock方法如果請求的鎖無法獲取或者在超時之前(如果指定)就會收回請求,如果在請求鎖之前另一個線程發送中斷信號,調用lockInterruptibly方法會收回請求。
正確使用鎖對象可以避免死鎖的產生,即如果一個線程需要對兩個對象進行同步操作,則確保同時獲得兩個對象的鎖就可以了,tryLock的典型用法為:
Lock lock = ...;//此處省略鎖的創建表達式 if (lock.tryLock()) { try { //如果成功獲得鎖情況下的操作 } finally {//操作之後釋放鎖 lock.unlock(); } } else { // 如果未能獲得鎖的操作 }
之前所有舉的例子中,都有和一個新的線程所作的工作緊密聯系,如類中定義的Runnable對象,和Thread對象定義的自身的線程,然而在大型的程序中,一般會將線程的管理和創建與程序的其他部分分開,而封裝了這些功能的對象叫做執行者,以下將執行者分三部分:
java.util.concurrent包中定義了三個執行者接口:
一般情況下,引用執行者對象的變量的類型都被聲明為以上三種接口類型,而不是實現接口的類的類型。
Executor接口提供了單一的方法,execute,其被設計為一個通常的線程對象創建的替代方法,如:
//將以下線程創建方法 (new Thread(r)).start(); //替換為 e.execute(r); //注意e是已經創建號的Executor對象
方法execute的定義不是很明確,低級的方法創建一個新線程對象後就立馬開始執行,而Executor對象根據具體的實現,可能也是立即執行,但更多的時候是利用已經存在的線程去運行示例中的r,或者將r放在隊列中等待運行(下面的線程池中會講到)。
java.util.concurrent包中的執行者實現類是為了充分更高級的ExecutorService 和ScheduledExecutorService接口而設計的,當然也適用於基礎的Executor接口。
ExecutorService接口對於execute方法補充了一個類似的,但是功能更強的submit方法,就像execute方法一樣,submit方法接收Runnable對象為參數,也可以接收允許任務返回一個值的Callable對象,submit方法返回一個Future對象,該Future對象可以檢索Callable對象返回的值,並可以管理Callable和Runnable任務的狀態。
ExecutorService接口還提供了支持提交大量Callable對象集合的方法,最後還有許多管理執行者終結的方法,如果需要立馬終結,則需要正確處理中斷。
ScheduledExecutorService接口除了繼承ExecutorService的特征外,還補充了schedule方法,該方法可以延遲執行Runnable和Callable對象,此外scheduleAtFixedRate和scheduleWithFixedDelay方法分別指定執行任務的重復和執行間隔。
大部分java.util.concurrent包中的執行者接口的實現類都使用線程池,線程池由多個工作線程組成,這種線程分別存在於其所執行的Runnable和Callable任務中。
線程池的使用使線程創建的開支最小化,線程對象需要使用大量的內存,而且在大型的程序中,線程對象的分配和解除分配會帶來大量的內存管理開支。
有一種常用的線程池叫固定線程池,這種線程池總是有指定數量的線程在運行,如果一個線程由於某種原因終止而線程池還在運行的時候,線程池就會自動替換新的線程。當活動的任務多余線程數量的時候,任務就會通過一個內部的隊列提交到線程池。
固定線程池的重要優勢就是應用程序可以利用其巧妙地降低負荷,比如一個web服務器應用程序中,每一個HTTP請求由一個獨立線程處理,如果該程序對於一個新的HTTP請求只是簡單地創建新的線程,隨著請求量增加,系統很快就沒有資源再創建更多的線程,程序因此可能會無法運行下去。當對線程創建的數量進行限制,並按照隊列依次處理,程序才可能有效運行。
簡單的創建一個使用固定線程池的執行者的方法就是調用java.util.concurrent.Executor的工廠方法newFixedThreadPool,這個類中也提供了一下工廠方法:
當以上工廠方法不能滿足需求,可以創建java.util.concurrent.ThreadPoolExecutor或者
java.util.concurrent.ScheduledThreadPoolExecutor實例以提供更豐富的功能。
Fork/Join框架是一個ExecutorService
接口的實現,幫助你充分利用多處理器的性能,它是為可以遞歸細分的任務而設計的,目的是使用所有的可使用的處理資源來增強程序的性能。
就像其他ExecutorService接口的額實現議案該,fork/join框架將任務分配給線程池中的工作線程,fork/join框架與眾不同的地方在於其“工作竊取“算法,已經處理完成任務的線程可以從其他正在繁忙的線程中竊取工作任務。
fork/join框架的核心是ForkJoinPool類,它是AbstractExecutorService類的擴展,ForkJoinPool類實現最核心的工作竊取算法,並且可以執行ForkJoinTask過程。
fork/join基本的用法就是先定義一個繼承ForkJoinTask類或者ForkJoinTask子類的類,用於處理部分的工作,定義好類以後,實例化該類並作為參數傳給ForkJoinPool
實例的invoke方法。
java se中有一些通用的特征已經用fork/join框架實現,如java8的java.util.Arrays類的parallelSort方法,這個方法與sort方法類似,但是利用了fork/join框架,在多處理器系統上,對於大型數組的並行排序比串行排序快。另外一個利用了fork/join框架的實現是java.util.streams包中的一些方法。