引言
IBM WebSphere Application Server 軟件提供了以下兩種機制,以支持 J2EE™ 應用程序開發人員在 Servlet 和 EJB 組件中安全地使用線程:
異步 Bean
Commonj Timer and WorkManager for Application Servers 1.1 規范。
兩種編程模式都允許您創建池線程和守護程序線程,以便運行 J2EE 業務邏輯。
在兩種編程模式中,線程可以由不同的應用程序重用。當應用程序邏輯開始和終止時,這可以通過應用和刪除線程內外的 J2EE 上下文信息來完成。因此,單個線程池可以由多個應用程序使用。每次使用線程時,其標識都會更改。
每次重用線程時,都必須更改線程的上下文,對於可能很少在這些線程執行活動的應用程序,開銷會特別大。在這些情況下,需要一個組件范圍的線程池,並在每個線程上具有固定的 J2EE 上下文。這可以通過使用 Asynchronous Beans EventSource 接口來完成。
本文介紹如何使用 Asynchronous Beans EventSource 構造線程工廠,文中還包括一個稱為並發適配器 (Concurrent Adapter) 的可下載示例,它可以與第三方線程池實現一起使用,以創建能夠在 WebSphere Application Server 上工作的快速線程池。
全局線程池
WebSphere Application Server 提供了高性能和高伸縮性的線程池實現。異步 Bean 和 Commonj 的 WorkManager 對所有池線程都使用此線程池。
由於 WorkManager 實例可用於全局命名空間,所以它們可以在多個應用程序之間共享,因此需要 J2EE 上下文切換。為完成此任務,WorkManager 在提交工作時會獲取線程上 J2EE 上下文的快照。產生的對象將成為 WorkWithExecutionContext (WWEC) 對象(圖 1)。
圖 1. WorkWithExecutionContext
如果將WorkManager 用作全局線程池(圖 2),則每次向線程分配工作時,提交給線程池的每項工作都將具有應用於線程(或從線程中刪除)的應用程序上下文:
將工作提交給 WorkManager 線程池(藍框)
獲取 J2EE 應用程序上下文的快照,並將其作為 WWEC 對象與該工作存儲在一起。
將 WWEC 添加到池的輸入隊列。
工作線程從輸入隊列提取一個 WWEC,並運行它。
獲取工作線程上當前 J2EE 應用程序上下文的快照,以便在工作完成之後進行恢復。
將與 WWEC 一起存儲的 J2EE 上下文應用到線程。
運行工作。
從線程刪除 J2EE 上下文,並重新應用以前的上下文。
工作線程現在等待更多工作出現在輸入隊列上。
圖 2. 與 WorkManager 共享的全局線程池
組件范圍的線程池
如果線程池僅由單個應用程序或組件(如 Servlet 或 EJB)使用,並且要提交的工作可以容許單一通用的 J2EE 上下文標識,那麼將自定義線程池與線程工廠一起使用可以顯著提高性能。這是組件范圍的線程池。
組件范圍的線程池線程都將共享創建它的應用程序組件的 J2EE 上下文。例如,如果由啟動 Bean 創建一個線程池,則每個線程將包含啟動 Bean 的 start() 方法的上下文,並且每個線程的行為就好像在該啟動 Bean 的范圍中運行一樣。因此,啟動 Bean 是線程池實例的所有者。不論提交工作的 Servlet 或 EJB 如何,所有業務邏輯都將在 java:comp 命名空間和啟動 Bean 的 start() 方法的安全上下文中運行。
組件范圍的線程池中的所有線程都是異步 Bean 守護程序線程,並且具有與創建它的應用程序相同的生命周期。如果應用程序結束,將調用池中每個守護程序工作線程的 release() 方法。
在使用自定義組件范圍的線程池(圖 3)時,將使用 WorkManager 創建的守護程序線程初始化池中的每個工作線程。WorkManager 成為線程工廠。每個線程將共享池創建程序的同一 J2EE 上下文:
將可運行線程提交到自定義線程池。
線程池工作線程從輸入隊列提取下一個 WWEC,並運行它。每個工作線程都有適用於它的線程池創建程序組件的 J2EE 上下文。
可運行線程在 J2EE 工作線程上運行。
當完成時,J2EE 工作線程仍保持活動狀態。
J2EE 工作線程現在等待更多工作出現在輸入隊列上。
圖 3. 組件范圍的線程池
自定義線程池
Asynchronous Beans WorkManager 不能外部化線程池,所以沒有任何方法更改缺省行為。要實現組件范圍的線程池,必須將第三方線程池與異步 Bean 的 J2EE 上下文切換功能結合使用。
現有的幾個線程池實現可以很好地與此模式一起使用。一種廣泛接受的實現是 Doug Lea 的 EDU.oswego.cs.dl.util.concurrent.PooledExecutor,它運行在 J2SE 1.2 及更高版本上。此線程池已發展為 J2SE 5 的 java.util.concurrent.ThreadPoolExecutor,它還向下移植到 J2SE 1.4。
PooledExecutor 和 ThreadPoolExecutor 實現都位於公共域中,並且可以下載。
下面是 WebSphere Application Server 各版本建議的線程池實現:
WebSphere Application Server Enterprise V5.0, J2SE 1.3:
EDU.oswego.cs.dl.util.concurrent.PooledExecutor
WebSphere Busness Integration Server Foundation V5.1 和
WebSphere Application Server(所有版本)V6.0,J2SE 1.4:
edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor
EDU.oswego.cs.dl.util.concurrent.PooledExecutor
WebSphere Application Server(所有版本)V6.1,J2SE 5:
edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor
EDU.oswego.cs.dl.util.concurrent.PooledExecutor
java.util.concurrent.ThreadPoolExecutor
上述實現非常相似,並且都利用了線程工廠,它們支持插入識別 J2EE 的自定義線程工廠。在這種情況下,自定義線程工廠是 Asynchronous Beans EventSource 的包裝程序。這些實現的行為類似於 WebSphere 線程池實現。如果使用的是 J2SE 1.4,則建議使用 java.util.concurrent 的向下移植,因為它具有更多的功能,並能夠簡化以後到 J2SE 5 的遷移。如果使用的是 J2SE 5,則建議直接使用 java.util.concurrent。
Asynchronous Beans EventSource
EventSource 是 Asynchronous Beans WorkManager 的一項機制,它支持 J2EE 上下文從一個線程到任何其他線程的動態應用。它提供了同一進程中的應用程序或安全上下文之間安全通信的方法。
例如,當股票價格改變時,如果要通知 Servlet,它可以在知名的 EventSource 上注冊一個偵聽器。當股票價格守護程序發布更改時,將通過系統帳戶通知每個偵聽器,但僅在偵聽器的安全上下文中進行通知(圖 4)。
圖 4. 使用 EventSource 的安全上下文切換
同一技術可以應用到任何 POJO(傳統 Java™ 對象),後者可以使用 EventSource 進行打包,以便針對單個方法調用切換對象的 J2EE 上下文。EventSources 使用 java.lang.reflect.Proxy 對象將每個對象實例與專用的 J2EE java.lang.reflect.InvocationHandler 打包在一起。使用代理來調用 Java 對象方法時,J2EE 處理程序將自動在注冊偵聽器時捕獲的線程上應用 J2EE 上下文。
當在范圍外的線程上執行方法時,此功能特別有用;例如,啟動線程池中的線程。
如果沒有 EventSource 代理,則新的線程將沒有 J2EE 上下文。線程池將實例化新的 java.util.Thread 對象,並調用 start() 方法。
EventSource 使我們能夠執行可以插入到 PooledExecutor 或 ThreadPoolExecutor 的 ThreadFactory 實現。創建 ThreadFactory 的 J2EE 組件的 J2EE 上下文將被保留,並在啟動新的工作線程之前重新應用到線程。切換上下文的所有開銷都一次性應用到線程。
由於 Commonj WorkManager 規范中不存在 EventSource 對象,所以不可能構建線程工廠。僅當使用異步 Bean 時,線程工廠才可使用。
實現線程工廠
在本文中,我們提供了示例線程工廠實現,這些實現支持使用 J2SE 5、J2SE 1.4 的組件范圍的自定義線程池(使用 J2SE 5 java.util.concurrent.ThreadPoolExecutor 的向下移植或在 WebSphere 應用程序中使用 dl.util.concurrent.PooledExecutor)。
WASThreadFactory
WASThreadFactory 是 ThreadFactory 的實現。它使用 WASThreadFactoryBase基類來支持使用 J2SE 5 和 ThreadFactory 的 dl.concurrent.util 版本的單個實現(圖 5)。
圖 5. WASThreadFactory 類關系圖
WASThreadFactory 接受 WorkManager 作為構造函數的參數,並使用它創建 EventSource 並為 WorkManager 注冊偵聽器代理。當從 WASThreadFactory 請求線程時,將使用 WorkManager 創建線程,但是該線程將包含創建 WASThreadFactory 實例的組件的 J2EE 上下文。這也稱為單上下文的線程池。
單上下文和多上下文的線程池
在使用自定義線程池(如 ThreadPoolExecutor)時,可能不需要在單個 J2EE 上下文中運行工作。例如,可能需要傳播提交程序的安全上下文。
在這種情況下,可以使用識別 J2EE 上下文的執行程序 (ContextExecutor),以便在執行之前將當前 J2EE 上下文附加到可運行線程。池中的每個工作線程都使用 WASThreadFactory 創建程序的 J2EE 上下文進行填充,但是當使用 ContextExecutor 時,可運行線程首先將提交程序的 J2EE 上下文應用到線程,然後在完成時刪除它。
使用同一 ThreadPoolExecutor 直接將工作提交到可以利用缺省 J2EE 上下文的池。使用 ContextExecutor 包裝程序將工作提交到 ThreadPoolExecutor,但要帶有提交程序的上下文。
圖 6. 帶有缺省的和多上下文的自定義線程池
用法示例:WASThreadFactory
要使用 WASThreadFactory,應用程序必須首先查找 Asynchronous Beans WorkManager,創建一個 WASThreadFactory 實例,然後構造新的線程池。下載文件示例中可提供使用 Servlet 的工作例子。
// Lookup the WorkManager and construct the WASThreadFactory
InitialContext ctx = new InitialContext();
WorkManager wm = (WorkManager)ctx.lookup("java:comp/env/wm/default");
ThreadFactory tf = new WASThreadFactory(wm);
// Create a ThreadPoolExecutor using a bounded buffer
BlockingQueue q = new ArrayBlockingQueue(10);
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, 10, 5000, TimeUnit.MILLISECONDS, q, tf);
// Use the submit or execute methods to submit work to the pool
pool.submit(myRunnable);
用法示例:ContextExecutor
使用帶 ContextExecutor 的 WASThreadFactory 將可運行線程提交給帶提交程序的 J2EE 上下文的自定義線程池與創建 ContextExecutor 實例一樣簡單。也可以使用 ContextExecutorService 來跟蹤提交的任務的狀態。
// Create a ThreadPoolExecutor
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, 10, 5000, TimeUnit.MILLISECONDS, q, tf);
// Wrap the ThreadPoolExecutor with a ContextExecutor
ContextExecutor cePool = new ContextExecutor(wm, pool);
// Use the ContextExecutor.execute() method to submit work to the pool.
cePool.execute(myRunnable)
安裝並運行示例
本文包括三個示例應用程序;分別對應一個描述的線程池實現。每個應用程序包含一個可以在 WebSphere Application Server 上安裝的 EAR。每個 EAR 包含:
一個 Web 模塊 (WAR)
ABConcurrencyUtils.jar 實用工具 JAR 文件,它包含對所有示例(包括 WASThreadFactory)通用的代碼
實用工具 JAR,它包括用於線程池實現的特定代碼。
每個 WAR 包含三個 Servlet:
FactoryTestServlet:一個簡單的示例,顯示如何創建 WASThreadFactory 並向其提交大量的可運行線程任務。
ABBenchmarkServlet:該示例顯示在不使用 WASThreadFactory 時運行異步 Bean 工作對象所用的微秒數。
FactoryBenchmarkServlet:該示例顯示在使用 WASThreadFactory 時運行可運行線程所用的微秒數。
每個模塊包括源和二進制,並可以直接將其導入到 IBM Rational® Application Developer V6。(在撰寫本文時,Rational Application Developer 當前不支持 JDK 5。JDK5 示例中的 JDK5 實用工具 JAR 將不能在 Rational Application Developer 中編譯,必須單獨構建。)
先決條件
每個示例都需要異步 Bean 和帶有 JNDI 名稱(缺省情況下為 wm)的 WorkManager;在安裝過程中缺省創建該 WorkManager。這些示例僅在 WebSphere Application Server 的單服務器版上進行了測試。盡管這些示例沒有在 WebSphere Application Server Network Deployment 或 Rational Application Developer 的單元測試環境或者 WebSphere Studio Application Developer Integration Edition 下進行測試,但我們預計這些示例可以在這些環境中按預期的方式運行。
示例 1:ABConcurrencyTester_JDK5.ear
可以在 WebSphere Application Server V6.1 及更高版本上安裝並運行此示例。它利用 J2SE 5 中包括的 java.util.concurrent.ThreadPoolExecutor。
要運行此示例,請按照下列步驟操作:
啟動 WebSphere Application Server。
使用 wsadmin 腳本或管理控制台安裝 ABConcurrencyTester_JDK5.ear。使用缺省選項。
啟動 ABConcurrencyTester_JDK5 應用程序。
使用以下 URL 運行示例(其中 <host> 是 IP 地址或應用服務器主機的名稱,<port> 是應用服務器的 HTTP 偵聽器端口):
http://<host>:<port>/ABConcurrencyTester_JDK5
示例 2:ABConcurrencyTester_JDK14.ear
可以在 IBM WebSphere Business Integration Server Foundation V5.1 和 WebSphere Application Server(所有版本)V6.0 及更高版本上安裝並運行此示例。它利用 JDK 1.4 的 java.util.concurrent 向下移植。
要運行此示例,請按照下列步驟操作:
從 http://dcl.mathcs.emory.edu/util/backport-util-concurrent/ 下載 backport-util-concurrent.jar。
將 backport-util-concurrent.jar 添加到 ABConcurrencyTester_JDK14.ear 的根目錄中。
使用 wsadmin 腳本或管理控制台安裝 ABConcurrencyTester_JDK14.ear。使用缺省選項。
啟動 ABConcurrencyTester_JDK14 應用程序。
使用以下 URL 運行示例(其中 <host> 是 IP 地址或應用服務器主機的名稱,<port> 是應用服務器的 HTTP 偵聽器端口):
http://<host>:<port>/ABConcurrencyTester_JDK14
示例 3:ABConcurrencyTester_DL.ear
此示例可以安裝在 WebSphere Application Server Enterprise V5.0.2、WebSphere Business Integration Server Foundation V5.1 和 WebSphere Application Server(所有版本)V6.0 及更高版本上。它利用運行在 JDK 1.2 及更高版本上的 dl.util.concurrent 包。
要運行此示例,請按照下列步驟操作:
使用 JDK 1.3 編譯器(如果部署到 WebSphere Application Server Enterprise V5.0 或更高版本)或 JDK 1.4 編譯器(如果部署到 WebSphere Business Integration Server Foundation V5.1 或更高版本)下載並構建 concurrent.jar。該實用工具附帶可以創建 concurrent.jar 的 ANT 腳本。(您可以使用應用服務器 bin 目錄中的 ws_ant.bat 或 ws_ant.sh 腳本來構建 concurrent.jar。)從 http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html 下載該實用工具。
將 concurrent.jar 重新命名為 dl-util-concurrent.jar。
將 dl-util-concurrent.jar 添加到 ABConcurrencyTester_DL.ear 的根目錄。
使用 wsadmin 腳本或管理控制台安裝 ABConcurrencyTester_DL.ear。使用缺省選項。
啟動 ABConcurrencyTester_DL 應用程序。
使用以下 URL 運行示例(其中 <host> 是 IP 地址或應用服務器主機的名稱,<port> 是應用服務器的 HTTP 偵聽器端口):
http://<host>:<port>/ABConcurrencyTester_DL
結束語
異步 Bean 提供可以由多個應用程序使用的有效而又安全的全局線程池。在需要專門的線程池時,可以使用 Asynchronous Beans EventSource 來創建 ThreadFactory。可以使用識別 J2EE 的 ThreadFactory 來創建使用固定 J2EE 上下文填充的線程。
在本文的 Concurrent Adapter 示例中使用的 WASThreadFactory 使 J2EE 應用程序開發人員能夠自由地利用任何高級線程使用模式,而不降低性能。