您好,登錄后才能下訂單哦!
這篇“Java中對(duì)象池如何實(shí)現(xiàn)”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來(lái)看看這篇“Java中對(duì)象池如何實(shí)現(xiàn)”文章吧。
池化并不是什么新鮮的技術(shù),它更像一種軟件設(shè)計(jì)模式,主要功能是緩存一組已經(jīng)初始化的對(duì)象,以供隨時(shí)可以使用。對(duì)象池大多數(shù)場(chǎng)景下都是緩存著創(chuàng)建成本過(guò)高或者需要重復(fù)創(chuàng)建使用的對(duì)象,從池子中取對(duì)象的時(shí)間是可以預(yù)測(cè)的,但是新建一個(gè)對(duì)象的時(shí)間是不確定的。
當(dāng)需要一個(gè)新對(duì)象時(shí),就向池中借出一個(gè),然后對(duì)象池標(biāo)記當(dāng)前對(duì)象正在使用,使用完畢后歸還到對(duì)象池,以便再次借出。
常見(jiàn)的使用對(duì)象池化場(chǎng)景:
1. 對(duì)象創(chuàng)建成本過(guò)高。
2. 需要頻繁的創(chuàng)建大量重復(fù)對(duì)象,會(huì)產(chǎn)生很多內(nèi)存碎片。
3. 同時(shí)使用的對(duì)象不會(huì)太多。
4. 常見(jiàn)的具體場(chǎng)景如數(shù)據(jù)庫(kù)連接池、線程池等。
如果一個(gè)對(duì)象的創(chuàng)建成本很高,比如建立數(shù)據(jù)庫(kù)的連接時(shí)耗時(shí)過(guò)長(zhǎng),在不使用池化技術(shù)的情況下,我們的查詢(xún)過(guò)程可能是這樣的。
查詢(xún) 1:建立數(shù)據(jù)庫(kù)連接 -> 發(fā)起查詢(xún) -> 收到響應(yīng) -> 關(guān)閉連接
查詢(xún) 2:建立數(shù)據(jù)庫(kù)連接 -> 發(fā)起查詢(xún) -> 收到響應(yīng) -> 關(guān)閉連接
查詢(xún) 3:建立數(shù)據(jù)庫(kù)連接 -> 發(fā)起查詢(xún) -> 收到響應(yīng) -> 關(guān)閉連接
在這種模式下,每次查詢(xún)都要重新建立關(guān)閉連接,因?yàn)榻⑦B接是一個(gè)耗時(shí)的操作,所以這種模式會(huì)影響程序的總體性能。
那么使用池化思想是怎么樣的呢?同樣的過(guò)程會(huì)轉(zhuǎn)變成下面的步驟。
初始化:建立 N 個(gè)數(shù)據(jù)庫(kù)連接 -> 緩存起來(lái)
查詢(xún) 1:從緩存借到數(shù)據(jù)庫(kù)連接 -> 發(fā)起查詢(xún) -> 收到響應(yīng) -> 歸還數(shù)據(jù)庫(kù)連接對(duì)象到緩存
查詢(xún) 2:從緩存借到數(shù)據(jù)庫(kù)連接 -> 發(fā)起查詢(xún) -> 收到響應(yīng) -> 歸還數(shù)據(jù)庫(kù)連接對(duì)象到緩存
查詢(xún) 3:從緩存借到數(shù)據(jù)庫(kù)連接 -> 發(fā)起查詢(xún) -> 收到響應(yīng) -> 歸還數(shù)據(jù)庫(kù)連接對(duì)象到緩存
使用池化思想后,數(shù)據(jù)庫(kù)連接并不會(huì)頻繁的創(chuàng)建關(guān)閉,而是啟動(dòng)后就初始化了 N 個(gè)連接以供后續(xù)使用,使用完畢后歸還對(duì)象,這樣程序的總體性能得到提升。
通過(guò)上面的例子也可以發(fā)現(xiàn)池化思想的幾個(gè)關(guān)鍵步驟:初始化、借出、歸還。上面沒(méi)有展示銷(xiāo)毀步驟, 某些場(chǎng)景下還需要對(duì)象的銷(xiāo)毀這一過(guò)程,比如釋放連接。
下面我們手動(dòng)實(shí)現(xiàn)一個(gè)簡(jiǎn)陋的對(duì)象池,加深下對(duì)對(duì)象池的理解。主要是定一個(gè)對(duì)象池管理類(lèi),然后在里面實(shí)現(xiàn)對(duì)象的初始化、借出、歸還、銷(xiāo)毀等操作。
package com.wdbyet.tool.objectpool.mypool; import java.io.Closeable; import java.io.IOException; import java.util.HashSet; import java.util.Stack; /** * @author https://www.wdbyte.com */ public class MyObjectPool<T extends Closeable> { // 池子大小 private Integer size = 5; // 對(duì)象池棧。后進(jìn)先出 private Stack<T> stackPool = new Stack<>(); // 借出的對(duì)象的 hashCode 集合 private HashSet<Integer> borrowHashCodeSet = new HashSet<>(); /** * 增加一個(gè)對(duì)象 * * @param t */ public synchronized void addObj(T t) { if ((stackPool.size() + borrowHashCodeSet.size()) == size) { throw new RuntimeException("池中對(duì)象已經(jīng)達(dá)到最大值"); } stackPool.add(t); System.out.println("添加了對(duì)象:" + t.hashCode()); } /** * 借出一個(gè)對(duì)象 * * @return */ public synchronized T borrowObj() { if (stackPool.isEmpty()) { System.out.println("沒(méi)有可以被借出的對(duì)象"); return null; } T pop = stackPool.pop(); borrowHashCodeSet.add(pop.hashCode()); System.out.println("借出了對(duì)象:" + pop.hashCode()); return pop; } /** * 歸還一個(gè)對(duì)象 * * @param t */ public synchronized void returnObj(T t) { if (borrowHashCodeSet.contains(t.hashCode())) { stackPool.add(t); borrowHashCodeSet.remove(t.hashCode()); System.out.println("歸還了對(duì)象:" + t.hashCode()); return; } throw new RuntimeException("只能歸還從池中借出的對(duì)象"); } /** * 銷(xiāo)毀池中對(duì)象 */ public synchronized void destory() { if (!borrowHashCodeSet.isEmpty()) { throw new RuntimeException("尚有未歸還的對(duì)象,不能關(guān)閉所有對(duì)象"); } while (!stackPool.isEmpty()) { T pop = stackPool.pop(); try { pop.close(); } catch (IOException e) { throw new RuntimeException(e); } } System.out.println("已經(jīng)銷(xiāo)毀了所有對(duì)象"); } }
代碼還是比較簡(jiǎn)單的,只是簡(jiǎn)單的示例,下面我們通過(guò)池化一個(gè) Redis 連接對(duì)象 Jedis 來(lái)演示如何使用。
其實(shí) Jedis 中已經(jīng)有對(duì)應(yīng)的 Jedis 池化管理對(duì)象了 JedisPool 了,不過(guò)我們這里為了演示對(duì)象池的實(shí)現(xiàn),就不使用官方提供的 JedisPool 了。
啟動(dòng)一個(gè) Redis 服務(wù)這里不做介紹,假設(shè)你已經(jīng)有了一個(gè) Redis 服務(wù),下面引入 Java 中連接 Redis 需要用到的 Maven 依賴(lài)。
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.2.0</version> </dependency>
正常情況下 Jedis 對(duì)象的使用方式:
Jedis jedis = new Jedis("localhost", 6379); String name = jedis.get("name"); System.out.println(name); jedis.close();
如果使用上面的對(duì)象池,就可以像下面這樣使用。
package com.wdbyet.tool.objectpool.mypool; import redis.clients.jedis.Jedis; /** * @author niulang * @date 2022/07/02 */ public class MyObjectPoolTest { public static void main(String[] args) { MyObjectPool<Jedis> objectPool = new MyObjectPool<>(); // 增加一個(gè) jedis 連接對(duì)象 objectPool.addObj(new Jedis("127.0.0.1", 6379)); objectPool.addObj(new Jedis("127.0.0.1", 6379)); // 從對(duì)象池中借出一個(gè) jedis 對(duì)象 Jedis jedis = objectPool.borrowObj(); // 一次 redis 查詢(xún) String name = jedis.get("name"); System.out.println(String.format("redis get:" + name)); // 歸還 redis 連接對(duì)象 objectPool.returnObj(jedis); // 銷(xiāo)毀對(duì)象池中的所有對(duì)象 objectPool.destory(); // 再次借用對(duì)象 objectPool.borrowObj(); } }
輸出日志:
添加了對(duì)象:1556956098
添加了對(duì)象:1252585652
借出了對(duì)象:1252585652
redis get:www.wdbyte.com
歸還了對(duì)象:1252585652
已經(jīng)銷(xiāo)毀了所有對(duì)象
沒(méi)有可以被借出的對(duì)象
如果使用 JMH 對(duì)使用對(duì)象池化進(jìn)行 Redis 查詢(xún),和正常創(chuàng)建 Redis 連接然后查詢(xún)關(guān)閉連接的方式進(jìn)行性能對(duì)比,會(huì)發(fā)現(xiàn)兩者的性能差異很大。下面是測(cè)試結(jié)果,可以發(fā)現(xiàn)使用對(duì)象池化后的性能是非池化方式的 5 倍左右。
Benchmark Mode Cnt Score Error Units
MyObjectPoolTest.test thrpt 15 2612.689 ± 358.767 ops/s
MyObjectPoolTest.testPool thrpt 9 12414.228 ± 11669.484 ops/s
上面自己實(shí)現(xiàn)的對(duì)象池總歸有些簡(jiǎn)陋了,其實(shí)開(kāi)源工具中已經(jīng)有了非常好用的對(duì)象池的實(shí)現(xiàn),如 Apache 的 commons-pool2
工具,很多開(kāi)源工具中的對(duì)象池都是基于此工具實(shí)現(xiàn),下面介紹這個(gè)工具的使用方式。
maven 依賴(lài):
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.11.1</version> </dependency>
在 commons-pool2
對(duì)象池工具中有幾個(gè)關(guān)鍵的類(lèi)。
• PooledObjectFactory
類(lèi)是一個(gè)工廠接口,用于實(shí)現(xiàn)想要池化對(duì)象的創(chuàng)建、驗(yàn)證、銷(xiāo)毀等操作。
• GenericObjectPool
類(lèi)是一個(gè)通用的對(duì)象池管理類(lèi),可以進(jìn)行對(duì)象的借出、歸還等操作。
• GenericObjectPoolConfig
類(lèi)是對(duì)象池的配置類(lèi),可以進(jìn)行對(duì)象的最大、最小等容量信息進(jìn)行配置。
下面通過(guò)一個(gè)具體的示例演示 commons-pool2
工具類(lèi)的使用,這里依舊選擇 Redis 連接對(duì)象 Jedis 作為演示。
實(shí)現(xiàn) PooledObjectFactory
工廠類(lèi),實(shí)現(xiàn)其中的對(duì)象創(chuàng)建和銷(xiāo)毀方法。
public class MyPooledObjectFactory implements PooledObjectFactory<Jedis> { @Override public void activateObject(PooledObject<Jedis> pooledObject) throws Exception { } @Override public void destroyObject(PooledObject<Jedis> pooledObject) throws Exception { Jedis jedis = pooledObject.getObject(); jedis.close(); System.out.println("釋放連接"); } @Override public PooledObject<Jedis> makeObject() throws Exception { return new DefaultPooledObject(new Jedis("localhost", 6379)); } @Override public void passivateObject(PooledObject<Jedis> pooledObject) throws Exception { } @Override public boolean validateObject(PooledObject<Jedis> pooledObject) { return false; } }
繼承 GenericObjectPool
類(lèi),實(shí)現(xiàn)對(duì)對(duì)象的借出、歸還等操作。
public class MyGenericObjectPool extends GenericObjectPool<Jedis> { public MyGenericObjectPool(PooledObjectFactory factory) { super(factory); } public MyGenericObjectPool(PooledObjectFactory factory, GenericObjectPoolConfig config) { super(factory, config); } public MyGenericObjectPool(PooledObjectFactory factory, GenericObjectPoolConfig config, AbandonedConfig abandonedConfig) { super(factory, config, abandonedConfig); } }
可以看到 MyGenericObjectPool
類(lèi)的構(gòu)造函數(shù)中的入?yún)⒂?GenericObjectPoolConfig
對(duì)象,這是個(gè)對(duì)象池的配置對(duì)象,可以配置對(duì)象池的容量大小等信息,這里就不配置了,使用默認(rèn)配置。
通過(guò) GenericObjectPoolConfig
的源碼可以看到默認(rèn)配置中,對(duì)象池的容量是 8 個(gè)。
public class GenericObjectPoolConfig<T> extends BaseObjectPoolConfig<T> { /** * The default value for the {@code maxTotal} configuration attribute. * @see GenericObjectPool#getMaxTotal() */ public static final int DEFAULT_MAX_TOTAL = 8; /** * The default value for the {@code maxIdle} configuration attribute. * @see GenericObjectPool#getMaxIdle() */ public static final int DEFAULT_MAX_IDLE = 8;
下面編寫(xiě)一個(gè)對(duì)象池使用測(cè)試類(lèi)。
public class ApachePool { public static void main(String[] args) throws Exception { MyGenericObjectPool objectMyObjectPool = new MyGenericObjectPool(new MyPooledObjectFactory()); Jedis jedis = objectMyObjectPool.borrowObject(); String name = jedis.get("name"); System.out.println(name); objectMyObjectPool.returnObject(jedis); objectMyObjectPool.close(); } }
輸出日志:
redis get:www.wdbyte.com
釋放連接
上面已經(jīng)演示了 commons-pool2
工具中的對(duì)象池的使用方式,從上面的例子中可以發(fā)現(xiàn)這種對(duì)象池中只能存放同一種初始化條件的對(duì)象,如果這里的 Redis 我們需要存儲(chǔ)一個(gè)本地連接和一個(gè)遠(yuǎn)程連接的兩種 Jedis 對(duì)象,就不能滿足了。那么怎么辦呢?
其實(shí) commons-pool2
工具已經(jīng)考慮到了這種情況,通過(guò)增加一個(gè) key 值可以在同一個(gè)對(duì)象池管理中進(jìn)行區(qū)分,代碼和上面類(lèi)似,直接貼出完整的代碼實(shí)現(xiàn)。
package com.wdbyet.tool.objectpool.apachekeyedpool; import org.apache.commons.pool2.BaseKeyedPooledObjectFactory; import org.apache.commons.pool2.KeyedPooledObjectFactory; import org.apache.commons.pool2.PooledObject; import org.apache.commons.pool2.impl.AbandonedConfig; import org.apache.commons.pool2.impl.DefaultPooledObject; import org.apache.commons.pool2.impl.GenericKeyedObjectPool; import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; import redis.clients.jedis.Jedis; /** * @author https://www.wdbyte.com * @date 2022/07/07 */ public class ApacheKeyedPool { public static void main(String[] args) throws Exception { String key = "local"; MyGenericKeyedObjectPool objectMyObjectPool = new MyGenericKeyedObjectPool(new MyKeyedPooledObjectFactory()); Jedis jedis = objectMyObjectPool.borrowObject(key); String name = jedis.get("name"); System.out.println("redis get :" + name); objectMyObjectPool.returnObject(key, jedis); } } class MyKeyedPooledObjectFactory extends BaseKeyedPooledObjectFactory<String, Jedis> { @Override public Jedis create(String key) throws Exception { if ("local".equals(key)) { return new Jedis("localhost", 6379); } if ("remote".equals(key)) { return new Jedis("192.168.0.105", 6379); } return null; } @Override public PooledObject<Jedis> wrap(Jedis value) { return new DefaultPooledObject<>(value); } } class MyGenericKeyedObjectPool extends GenericKeyedObjectPool<String, Jedis> { public MyGenericKeyedObjectPool(KeyedPooledObjectFactory<String, Jedis> factory) { super(factory); } public MyGenericKeyedObjectPool(KeyedPooledObjectFactory<String, Jedis> factory, GenericKeyedObjectPoolConfig<Jedis> config) { super(factory, config); } public MyGenericKeyedObjectPool(KeyedPooledObjectFactory<String, Jedis> factory, GenericKeyedObjectPoolConfig<Jedis> config, AbandonedConfig abandonedConfig) { super(factory, config, abandonedConfig); } }
輸出日志:
redis get :www.wdbyte.com
這篇文章中的演示都使用了 Jedis 連接對(duì)象,其實(shí)在 Jedis SDK 中已經(jīng)實(shí)現(xiàn)了相應(yīng)的對(duì)象池,也就是我們常用的 JedisPool 類(lèi)。那么這里的 JedisPool 是怎么實(shí)現(xiàn)的呢?我們先看一下 JedisPool 的使用方式。
package com.wdbyet.tool.objectpool; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; /** * @author https://www.wdbyte.com */ public class JedisPoolTest { public static void main(String[] args) { JedisPool jedisPool = new JedisPool("localhost", 6379); // 從對(duì)象池中借一個(gè)對(duì)象 Jedis jedis = jedisPool.getResource(); String name = jedis.get("name"); System.out.println("redis get :" + name); jedis.close(); // 徹底退出前,關(guān)閉 Redis 連接池 jedisPool.close(); } }
代碼中添加了注釋?zhuān)梢钥吹酵ㄟ^(guò) jedisPool.getResource()
拿到了一個(gè)對(duì)象,這里和上面 commons-pool2
工具中的 borrowObject
十分相似,繼續(xù)追蹤它的代碼實(shí)現(xiàn)可以看到下面的代碼。
// redis.clients.jedis.JedisPool // public class JedisPool extends Pool<Jedis> { public Jedis getResource() { Jedis jedis = (Jedis)super.getResource(); jedis.setDataSource(this); return jedis; } // 繼續(xù)追蹤 super.getResource() // redis.clients.jedis.util.Pool public T getResource() { try { return super.borrowObject(); } catch (JedisException var2) { throw var2; } catch (Exception var3) { throw new JedisException("Could not get a resource from the pool", var3); } }
竟然看到了 super.borrowObject()
,多么熟悉的方法,繼續(xù)分析代碼可以發(fā)現(xiàn) Jedis 對(duì)象池也是使用了 commons-pool2
工具作為實(shí)現(xiàn)。既然如此,那么 jedis.close()
方法的邏輯我們應(yīng)該也可以猜到了,應(yīng)該有一個(gè)歸還的操作,查看代碼發(fā)現(xiàn)果然如此。
// redis.clients.jedis.JedisPool // public class JedisPool extends Pool<Jedis> { public void close() { if (this.dataSource != null) { Pool<Jedis> pool = this.dataSource; this.dataSource = null; if (this.isBroken()) { pool.returnBrokenResource(this); } else { pool.returnResource(this); } } else { this.connection.close(); } } // 繼續(xù)追蹤 super.getResource() // redis.clients.jedis.util.Pool public void returnResource(T resource) { if (resource != null) { try { super.returnObject(resource); } catch (RuntimeException var3) { throw new JedisException("Could not return the resource to the pool", var3); } } }
通過(guò)上面的分析,可見(jiàn) Jedis 確實(shí)使用了 commons-pool2
工具進(jìn)行對(duì)象池的管理,通過(guò)分析 JedisPool 類(lèi)的繼承關(guān)系圖也可以發(fā)現(xiàn)。
以上就是關(guān)于“Java中對(duì)象池如何實(shí)現(xiàn)”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。