|
Posted on 2011-04-29 14:39 點(diǎn)點(diǎn)滴滴 閱讀(627) 評(píng)論(0) 編輯 收藏 引用
1 對(duì)象池技術(shù)的原理:
對(duì)象池技術(shù)在服務(wù)器開(kāi)發(fā)上應(yīng)用廣泛。在各種對(duì)象池的實(shí)現(xiàn)中,尤其以數(shù)據(jù)庫(kù)的連接池最為明顯,可以說(shuō)是每個(gè)服務(wù)器必須實(shí)現(xiàn)的部分。本文是個(gè)人學(xué)習(xí)對(duì)象池的一個(gè)記錄,以Apache的commons-pool實(shí)現(xiàn)為研究對(duì)象。在第二部分中,本人將繼續(xù)研究Apache的common-dbcp,這是對(duì)象池技術(shù)在JDBC上的一個(gè)應(yīng)用范例。 ObjectPool維護(hù)一個(gè)列表,其中存放所有已經(jīng)生成的對(duì)象。同時(shí)導(dǎo)出幾個(gè)方法,如 borrowObject,returnObject,addObject等等。當(dāng)用戶(hù)調(diào)用borrowObject時(shí),ObjectPool查看當(dāng)前列表中的空閑對(duì)象的數(shù)目,如果有空閑的對(duì)象,則初始化該對(duì)象后返回給用戶(hù),否則創(chuàng)建一個(gè)對(duì)象,返回給用戶(hù)使用。同理,當(dāng)用戶(hù)調(diào)用returnObject 時(shí),ObjectPool查看當(dāng)前隊(duì)列中的空閑對(duì)象數(shù)目,如果數(shù)目小于DEFAULT_MAX_SLEEPING,則將改對(duì)象的狀態(tài)清空,然后放到隊(duì)列中,作為備用對(duì)象;否則直接銷(xiāo)毀該對(duì)象。 對(duì)象池中還涉及到一些高級(jí)的技術(shù), 比如過(guò)期銷(xiāo)毀, 被破壞時(shí)銷(xiāo)毀, 對(duì)象數(shù)超過(guò)池大小銷(xiāo)毀, 對(duì)象池中沒(méi)有可用空閑對(duì)象時(shí)等待等等.
apache的common-pool工具庫(kù)是對(duì)池化技術(shù)原理的一種具體實(shí)現(xiàn). 在闡述原理之前, 這里先理解幾個(gè)概念:
對(duì)象池(ObjectPool接口): 可以把它認(rèn)為是一種容器, 它是用來(lái)裝池對(duì)象的, 并且包含了用來(lái)創(chuàng)建池對(duì)象的工廠對(duì)象 池對(duì)象:就是要放到池容器中的對(duì)象, 理論上可以是任何對(duì)象. 對(duì)象池工廠(ObjectPoolFactory接口):用來(lái)創(chuàng)建對(duì)象池的工廠, 這個(gè)沒(méi)什么好說(shuō)的. 池對(duì)象工廠(PoolableObjectFactory 接口):用來(lái)創(chuàng)建池對(duì)象, 將不用的池對(duì)象進(jìn)行鈍化(passivateObject), 對(duì)要使用的池對(duì)象進(jìn)行激活(activeObject), 對(duì)池對(duì)象進(jìn)行驗(yàn)證(validateObject), 對(duì)有問(wèn)題的池對(duì)象進(jìn)行銷(xiāo)毀(destroyObject)等工作
對(duì)象池中封裝了創(chuàng)建, 獲取, 歸還, 銷(xiāo)毀池對(duì)象的職責(zé), 當(dāng)然這些工作都是通過(guò)池對(duì)象工廠來(lái)實(shí)施的, 容器內(nèi)部還有一個(gè)或多個(gè)用來(lái)盛池對(duì)象的容器.對(duì)象池會(huì)對(duì)容器大小, 存放時(shí)間, 訪問(wèn)等待時(shí)間, 空閑時(shí)間等等進(jìn)行一些控制, 因?yàn)榭梢愿鶕?jù)需要來(lái)調(diào)整這些設(shè)置.
當(dāng)需要拿一個(gè)池對(duì)象的時(shí)候, 就從容器中取出一個(gè), 如果容器中沒(méi)有的話(huà), 而且又沒(méi)有達(dá)到容器的最大限制, 那么就調(diào)用池對(duì)象工廠, 新建一個(gè)池對(duì)象, 并調(diào)用工廠的激活方法, 對(duì)創(chuàng)建的對(duì)象進(jìn)行激活, 驗(yàn)證等一系列操作. 如果已經(jīng)達(dá)到池容器的最大值, 而對(duì)象池中又經(jīng)沒(méi)有空閑的對(duì)象, 那么將會(huì)繼續(xù)等待, 直到有新的空閑的對(duì)象被丟進(jìn)來(lái), 當(dāng)然這個(gè)等待也是有限度的, 如果超出了這個(gè)限度, 對(duì)象池就會(huì)拋出異常.
“出來(lái)混, 總是要還的”, 池對(duì)象也是如此, 當(dāng)將用完的池對(duì)象歸還到對(duì)象池中的時(shí)候, 對(duì)象池會(huì)調(diào)用池對(duì)象工廠對(duì)該池對(duì)象進(jìn)行驗(yàn)證, 如果驗(yàn)證不通過(guò)則被認(rèn)為是有問(wèn)題的對(duì)象, 將會(huì)被銷(xiāo)毀, 同樣如果容器已經(jīng)滿(mǎn)了, 這個(gè)歸還池對(duì)象將變的”無(wú)家可歸”, 也會(huì)被銷(xiāo)毀, 如果不屬于上面兩種情況, 對(duì)象池就會(huì)調(diào)用工廠對(duì)象將其鈍化并放入容器中. 在整個(gè)過(guò)程中, 激活, 檢查, 鈍化處理都不是必須的, 因此我們?cè)趯?shí)PoolableObjectFactory接口的時(shí)候, 一般不作處理, 給空實(shí)現(xiàn)即可, 所以誕生了BasePoolableObjectFactory.
當(dāng)然你也可以將要已有的對(duì)象創(chuàng)建好, 然后通過(guò)addObject放到對(duì)象池中去, 以備后用.
為了確保對(duì)對(duì)象池的訪問(wèn)都是線程安全的, 所有對(duì)容器的操作都必須放在synchronized中.
這種備用的觀念正是對(duì)象池的理論基礎(chǔ),可以很大程度上減少對(duì)象生成和銷(xiāo)毀的次數(shù)。對(duì)于那些初始化過(guò)程很慢的對(duì)象來(lái)說(shuō),減少對(duì)象構(gòu)造和銷(xiāo)毀的次數(shù)就等于大幅度提高了整體效率。特別是對(duì)于數(shù)據(jù)庫(kù)連接這樣的對(duì)象,由于進(jìn)行JNDI搜索的效率極為低下,應(yīng)用對(duì)象池技術(shù)是理所當(dāng)然的。 需要注意的是,對(duì)象池技術(shù)并不是對(duì)任何對(duì)象都適用。因?yàn)閷?duì)象池本身的操作要耗費(fèi)一些資源,對(duì)于一些小對(duì)象來(lái)說(shuō),使用對(duì)象池可能取得相反的效果。IBM DeveloperWorks上有一篇論文,指出簡(jiǎn)單對(duì)象如Point,Size等,使用對(duì)象池技術(shù)并不能帶來(lái)性能的改善;而復(fù)雜對(duì)象如 JPanel,JFrame等,使用對(duì)象池后能帶來(lái)稍微的性能優(yōu)勢(shì);最最適合對(duì)象池技術(shù)的是一些耗時(shí)操作,如JDBC連接,線程等。
2 研究 Apache common pool
對(duì)象池結(jié)構(gòu):
在 apache的common-pool工具庫(kù)中有5種對(duì)象池:GenericObjectPool和 GenericKeyedObjectPool, SoftReferenceObjectPool, StackObjectPool, StackKeyedObjectPool. 五種對(duì)象池可分為兩類(lèi), 一類(lèi)是無(wú)key的(有key的類(lèi)圖相似):
前面兩種用CursorableLinkedList來(lái)做容器
SoftReferenceObjectPool用ArrayList做容器, 一次性創(chuàng)建所有池化對(duì)象, 并對(duì)容器中的對(duì)象進(jìn)行了軟引用(SoftReference)處理, 從而保證在內(nèi)存充足的時(shí)候池對(duì)象不會(huì)輕易被jvm垃圾回收, 從而具有很強(qiáng)的緩存能力.
最后兩種用Stack做容器. 不帶key的對(duì)象池是對(duì)前面池技術(shù)原理的一種簡(jiǎn)單實(shí)現(xiàn), 帶key的相對(duì)復(fù)雜一些, 它會(huì)將池對(duì)象按照key來(lái)進(jìn)行分類(lèi), 具有相同的key被劃分到一組類(lèi)別中, 因此有多少個(gè)key, 就會(huì)有多少個(gè)容器. 之所以需要帶key的這種對(duì)象池, 是因?yàn)槠胀ǖ膶?duì)象池通過(guò)makeObject()方法創(chuàng)建的對(duì)象基本上都是一模一樣的, 因?yàn)闆](méi)法傳遞參數(shù)來(lái)對(duì)池對(duì)象進(jìn)行定制.
因此四種池對(duì)象的區(qū)別主要體現(xiàn)在內(nèi)部的容器的區(qū)別, Stack遵循”后進(jìn)先出”的原則并能保證線程安全, CursorableLinkedList是一個(gè)內(nèi)部用游標(biāo)(cursor)來(lái)定位當(dāng)前元素的雙向鏈表, 是非線程安全的, 但是能滿(mǎn)足對(duì)容器的并發(fā)修改.ArrayList是非線程安全的, 便利方便的容器.
使用對(duì)象池的一般步驟:創(chuàng)建一個(gè)池對(duì)象工廠, 將該工廠注入到對(duì)象池中, 當(dāng)要取池對(duì)象, 調(diào)用borrowObject, 當(dāng)要?dú)w還池對(duì)象時(shí), 調(diào)用returnObject, 銷(xiāo)毀池對(duì)象調(diào)用clear(), 如果要連池對(duì)象工廠也一起銷(xiāo)毀, 則調(diào)用close(). 下面是一些時(shí)序圖:  
common-dbcp的結(jié)構(gòu) apache的連接池工具庫(kù)common-dbcp是common-pool在數(shù)據(jù)庫(kù)訪問(wèn)方面的一個(gè)具體應(yīng)用.當(dāng)對(duì)common-pool熟悉之后, 對(duì)common-dbcp就很好理解了. 它通過(guò)對(duì)已有的Connection, Statment對(duì)象包裝成池對(duì)象PoolableConnection, PoolablePreparedStatement. 然后在這些池化的對(duì)象中, 持有一個(gè)對(duì)對(duì)象池的引用, 在關(guān)閉的時(shí)候, 不進(jìn)行真正的關(guān)閉處理, 而是通過(guò)調(diào)用: 1. _pool.returnObject(this); 或: 1. _pool.returnObject(_key,this); 這樣一句, 將連接對(duì)象放回連接池中. 而對(duì)應(yīng)的對(duì)象池前者采用的是ObjectPool, 后者是KeyedObjectPool, 因?yàn)橐粋€(gè)數(shù)據(jù)庫(kù)只對(duì)應(yīng)一個(gè)連接, 而執(zhí)行操作的Statement卻根據(jù)Sql的不同會(huì)分很多種. 因此需要根據(jù)sql語(yǔ)句的不同多次進(jìn)行緩存 在對(duì)連接池的管理上, common-dbcp主要采用兩種對(duì)象: 一個(gè)是PoolingDriver, 另一個(gè)是PoolingDataSource, 二者的區(qū)別是PoolingDriver是一個(gè)更底層的操作類(lèi), 它持有一個(gè)連接池映射列表, 一般針對(duì)在一個(gè)jvm中要連接多個(gè)數(shù)據(jù)庫(kù), 而后者相對(duì)簡(jiǎn)單一些. 內(nèi)部只能持有一個(gè)連接池, 即一個(gè)數(shù)據(jù)源對(duì)應(yīng)一個(gè)連接池. 下面是common-dbcp的結(jié)構(gòu)關(guān)系:
下面是參考了common-dbcp的例子之后寫(xiě)的一個(gè)從連接池中獲取連接的工具類(lèi)
1. /**
2. * 創(chuàng)建連接
3. *
4. * @since 2009-1-22 下午02:58:35
5. */
6. public class ConnectionUtils {
7. // 一些common-dbcp內(nèi)部定義的protocol
8. private static final String POOL_DRIVER_KEY = "jdbc:apache:commons:dbcp:";
9. private static final String POLLING_DRIVER = "org.apache.commons.dbcp.PoolingDriver";
10.
11. /**
12. * 取得池化驅(qū)動(dòng)器
13. *
14. * @return
15. * @throws ClassNotFoundException
16. * @throws SQLException
17. */
18. private static PoolingDriver getPoolDriver() throws ClassNotFoundException,
19. SQLException {
20. Class.forName(POLLING_DRIVER);
21. return (PoolingDriver) DriverManager.getDriver(POOL_DRIVER_KEY);
22. }
23.
24. /**
25. * 銷(xiāo)毀所有連接
26. *
27. * @throws Exception
28. */
29. public static void destory() throws Exception {
30. PoolingDriver driver = getPoolDriver();
31. String[] names = driver.getPoolNames();
32. for (String name : names) {
33. driver.getConnectionPool(name).close();
34. }
35. }
36.
37. /**
38. * 從連接池中獲取數(shù)據(jù)庫(kù)連接
39. */
40. public static Connection getConnection(TableMetaData table)
41. throws Exception {
42. String key = table.getConnectionKey();
43.
44. PoolingDriver driver = getPoolDriver();
45.
46. ObjectPool pool = null;
47. // 這里找不到連接池會(huì)拋異常, 需要catch一下
48. try {
49. pool = driver.getConnectionPool(key);
50. } catch (Exception e) {
51. }
52.
53. if (pool == null) {
54. // 根據(jù)數(shù)據(jù)庫(kù)類(lèi)型構(gòu)建連接工廠
55. ConnectionFactory connectionFactory = null;
56. if (table.getDbAddr() != null
57. && TableMetaData.DB_TYPE_MYSQL == table.getDbType()) {
58. Class.forName(TableMetaData.MYSQL_DRIVER);
59. connectionFactory = new DriverManagerConnectionFactory(table
60. .getDBUrl(), null);
61. } else {
62. Class.forName(TableMetaData.ORACLE_DRIVER);
63. connectionFactory = new DriverManagerConnectionFactory(table
64. .getDBUrl(), table.getDbuser(), table.getDbpass());
65. }
66.
67. // 構(gòu)造連接池
68. ObjectPool connectionPool = new GenericObjectPool(null);
69. new PoolableConnectionFactory(connectionFactory, connectionPool,
70. null, null, false, true);
71.
72. // 將連接池注冊(cè)到driver中
73. driver.registerPool(key, connectionPool);
74. }
75.
76. // 從連接池中拿一個(gè)連接
77. return DriverManager.getConnection(POOL_DRIVER_KEY + key);
78. }
79.
80. }
雖然對(duì)象池技術(shù)在實(shí)際開(kāi)發(fā)過(guò)程中用的不是很多, 但是理解之后對(duì)我們寫(xiě)程序還是有莫大的好處的, 至少我是這樣的
3 用法:
dbcp([url]http://jakarta.apache.org/commons/dbcp/[/url])這個(gè)apache的開(kāi)源的數(shù)據(jù)庫(kù)連接池。結(jié)果發(fā)現(xiàn)dbcp依賴(lài)Apache common pool([url]http://jakarta.apache.org/commons/pool/[/url])
[url]http://www.ibm.com/developerworks/cn/java/l-common-pool/index.html#4[/url]
|