您好,登錄后才能下訂單哦!
這篇“Java中對象池如何實現”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Java中對象池如何實現”文章吧。
池化并不是什么新鮮的技術,它更像一種軟件設計模式,主要功能是緩存一組已經初始化的對象,以供隨時可以使用。對象池大多數場景下都是緩存著創建成本過高或者需要重復創建使用的對象,從池子中取對象的時間是可以預測的,但是新建一個對象的時間是不確定的。
當需要一個新對象時,就向池中借出一個,然后對象池標記當前對象正在使用,使用完畢后歸還到對象池,以便再次借出。
常見的使用對象池化場景:
1. 對象創建成本過高。
2. 需要頻繁的創建大量重復對象,會產生很多內存碎片。
3. 同時使用的對象不會太多。
4. 常見的具體場景如數據庫連接池、線程池等。
如果一個對象的創建成本很高,比如建立數據庫的連接時耗時過長,在不使用池化技術的情況下,我們的查詢過程可能是這樣的。
查詢 1:建立數據庫連接 -> 發起查詢 -> 收到響應 -> 關閉連接
查詢 2:建立數據庫連接 -> 發起查詢 -> 收到響應 -> 關閉連接
查詢 3:建立數據庫連接 -> 發起查詢 -> 收到響應 -> 關閉連接
在這種模式下,每次查詢都要重新建立關閉連接,因為建立連接是一個耗時的操作,所以這種模式會影響程序的總體性能。
那么使用池化思想是怎么樣的呢?同樣的過程會轉變成下面的步驟。
初始化:建立 N 個數據庫連接 -> 緩存起來
查詢 1:從緩存借到數據庫連接 -> 發起查詢 -> 收到響應 -> 歸還數據庫連接對象到緩存
查詢 2:從緩存借到數據庫連接 -> 發起查詢 -> 收到響應 -> 歸還數據庫連接對象到緩存
查詢 3:從緩存借到數據庫連接 -> 發起查詢 -> 收到響應 -> 歸還數據庫連接對象到緩存
使用池化思想后,數據庫連接并不會頻繁的創建關閉,而是啟動后就初始化了 N 個連接以供后續使用,使用完畢后歸還對象,這樣程序的總體性能得到提升。
通過上面的例子也可以發現池化思想的幾個關鍵步驟:初始化、借出、歸還。上面沒有展示銷毀步驟, 某些場景下還需要對象的銷毀這一過程,比如釋放連接。
下面我們手動實現一個簡陋的對象池,加深下對對象池的理解。主要是定一個對象池管理類,然后在里面實現對象的初始化、借出、歸還、銷毀等操作。
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; // 對象池棧。后進先出 private Stack<T> stackPool = new Stack<>(); // 借出的對象的 hashCode 集合 private HashSet<Integer> borrowHashCodeSet = new HashSet<>(); /** * 增加一個對象 * * @param t */ public synchronized void addObj(T t) { if ((stackPool.size() + borrowHashCodeSet.size()) == size) { throw new RuntimeException("池中對象已經達到最大值"); } stackPool.add(t); System.out.println("添加了對象:" + t.hashCode()); } /** * 借出一個對象 * * @return */ public synchronized T borrowObj() { if (stackPool.isEmpty()) { System.out.println("沒有可以被借出的對象"); return null; } T pop = stackPool.pop(); borrowHashCodeSet.add(pop.hashCode()); System.out.println("借出了對象:" + pop.hashCode()); return pop; } /** * 歸還一個對象 * * @param t */ public synchronized void returnObj(T t) { if (borrowHashCodeSet.contains(t.hashCode())) { stackPool.add(t); borrowHashCodeSet.remove(t.hashCode()); System.out.println("歸還了對象:" + t.hashCode()); return; } throw new RuntimeException("只能歸還從池中借出的對象"); } /** * 銷毀池中對象 */ public synchronized void destory() { if (!borrowHashCodeSet.isEmpty()) { throw new RuntimeException("尚有未歸還的對象,不能關閉所有對象"); } while (!stackPool.isEmpty()) { T pop = stackPool.pop(); try { pop.close(); } catch (IOException e) { throw new RuntimeException(e); } } System.out.println("已經銷毀了所有對象"); } }
代碼還是比較簡單的,只是簡單的示例,下面我們通過池化一個 Redis 連接對象 Jedis 來演示如何使用。
其實 Jedis 中已經有對應的 Jedis 池化管理對象了 JedisPool 了,不過我們這里為了演示對象池的實現,就不使用官方提供的 JedisPool 了。
啟動一個 Redis 服務這里不做介紹,假設你已經有了一個 Redis 服務,下面引入 Java 中連接 Redis 需要用到的 Maven 依賴。
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.2.0</version> </dependency>
正常情況下 Jedis 對象的使用方式:
Jedis jedis = new Jedis("localhost", 6379); String name = jedis.get("name"); System.out.println(name); jedis.close();
如果使用上面的對象池,就可以像下面這樣使用。
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<>(); // 增加一個 jedis 連接對象 objectPool.addObj(new Jedis("127.0.0.1", 6379)); objectPool.addObj(new Jedis("127.0.0.1", 6379)); // 從對象池中借出一個 jedis 對象 Jedis jedis = objectPool.borrowObj(); // 一次 redis 查詢 String name = jedis.get("name"); System.out.println(String.format("redis get:" + name)); // 歸還 redis 連接對象 objectPool.returnObj(jedis); // 銷毀對象池中的所有對象 objectPool.destory(); // 再次借用對象 objectPool.borrowObj(); } }
輸出日志:
添加了對象:1556956098
添加了對象:1252585652
借出了對象:1252585652
redis get:www.wdbyte.com
歸還了對象:1252585652
已經銷毀了所有對象
沒有可以被借出的對象
如果使用 JMH 對使用對象池化進行 Redis 查詢,和正常創建 Redis 連接然后查詢關閉連接的方式進行性能對比,會發現兩者的性能差異很大。下面是測試結果,可以發現使用對象池化后的性能是非池化方式的 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
上面自己實現的對象池總歸有些簡陋了,其實開源工具中已經有了非常好用的對象池的實現,如 Apache 的 commons-pool2
工具,很多開源工具中的對象池都是基于此工具實現,下面介紹這個工具的使用方式。
maven 依賴:
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.11.1</version> </dependency>
在 commons-pool2
對象池工具中有幾個關鍵的類。
• PooledObjectFactory
類是一個工廠接口,用于實現想要池化對象的創建、驗證、銷毀等操作。
• GenericObjectPool
類是一個通用的對象池管理類,可以進行對象的借出、歸還等操作。
• GenericObjectPoolConfig
類是對象池的配置類,可以進行對象的最大、最小等容量信息進行配置。
下面通過一個具體的示例演示 commons-pool2
工具類的使用,這里依舊選擇 Redis 連接對象 Jedis 作為演示。
實現 PooledObjectFactory
工廠類,實現其中的對象創建和銷毀方法。
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
類,實現對對象的借出、歸還等操作。
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
類的構造函數中的入參有 GenericObjectPoolConfig
對象,這是個對象池的配置對象,可以配置對象池的容量大小等信息,這里就不配置了,使用默認配置。
通過 GenericObjectPoolConfig
的源碼可以看到默認配置中,對象池的容量是 8 個。
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;
下面編寫一個對象池使用測試類。
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
釋放連接
上面已經演示了 commons-pool2
工具中的對象池的使用方式,從上面的例子中可以發現這種對象池中只能存放同一種初始化條件的對象,如果這里的 Redis 我們需要存儲一個本地連接和一個遠程連接的兩種 Jedis 對象,就不能滿足了。那么怎么辦呢?
其實 commons-pool2
工具已經考慮到了這種情況,通過增加一個 key 值可以在同一個對象池管理中進行區分,代碼和上面類似,直接貼出完整的代碼實現。
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 連接對象,其實在 Jedis SDK 中已經實現了相應的對象池,也就是我們常用的 JedisPool 類。那么這里的 JedisPool 是怎么實現的呢?我們先看一下 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); // 從對象池中借一個對象 Jedis jedis = jedisPool.getResource(); String name = jedis.get("name"); System.out.println("redis get :" + name); jedis.close(); // 徹底退出前,關閉 Redis 連接池 jedisPool.close(); } }
代碼中添加了注釋,可以看到通過 jedisPool.getResource()
拿到了一個對象,這里和上面 commons-pool2
工具中的 borrowObject
十分相似,繼續追蹤它的代碼實現可以看到下面的代碼。
// redis.clients.jedis.JedisPool // public class JedisPool extends Pool<Jedis> { public Jedis getResource() { Jedis jedis = (Jedis)super.getResource(); jedis.setDataSource(this); return jedis; } // 繼續追蹤 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()
,多么熟悉的方法,繼續分析代碼可以發現 Jedis 對象池也是使用了 commons-pool2
工具作為實現。既然如此,那么 jedis.close()
方法的邏輯我們應該也可以猜到了,應該有一個歸還的操作,查看代碼發現果然如此。
// 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(); } } // 繼續追蹤 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); } } }
通過上面的分析,可見 Jedis 確實使用了 commons-pool2
工具進行對象池的管理,通過分析 JedisPool 類的繼承關系圖也可以發現。
以上就是關于“Java中對象池如何實現”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。