Pooling is not a new technology. It is more like a software design pattern. Its main function is to cache a set of initialized objects so that they can be used at any time. In most scenarios, object pools cache objects that are too expensive to create or need to be created and used repeatedly. The time to retrieve an object from the pool is predictable, but the time to create a new object is uncertain.
When a new object is needed, one is borrowed from the pool, and then the object pool marks that the current object is in use. After use, it is returned to the object pool so that it can be lent out again.
Common scenarios for using object pooling:
1. The cost of object creation is too high.
2. Frequently creating a large number of duplicate objects will produce a lot of memory fragmentation.
3. There will not be too many objects used at the same time.
4. Common specific scenarios such as database connection pool, thread pool, etc.
If the creation cost of an object is very high, for example, it takes too long to establish a database connection, Without using pooling technology, our query process might look like this.
Query 1: Establish database connection -> Initiate query -> Receive response -> Close connection
Query 2: Establish Database connection -> Initiate query -> Receive response -> Close connection
Query 3: Establish database connection -> Initiate query -> Receive response -> Close the connection
In this mode, the closed connection must be re-established for each query. Because establishing a connection is a time-consuming operation, this mode will affect the overall performance of the program. .
So what is it like to use pooling thinking? The same process transforms into the following steps.
Initialization: Establish N database connections -> Cache them
Query 1: Borrow database connections from cache -> Initiate query -> Receive response -> Return database connection object to cache
Query 2: Borrow database connection from cache -> Initiate query -> Receive response -> Return the database connection object to the cache
Query 3: Borrow the database connection from the cache -> Initiate a query -> Receive the response -> Return the database connection object to the cache
After using the pooling idea, database connections will not be frequently created and closed. Instead, N connections will be initialized for subsequent use after startup, and the objects will be returned after use. In this way, the overall program Performance is improved.
Through the above example, we can also find several key steps of the pooling idea: Initialization, lending, and return. The destruction steps are not shown above. In some scenarios, the object destruction process is also required, such as releasing the connection.
Below we manually implement a simple object pool to deepen our understanding of the object pool. The main thing is to define an object pool management class, and then implement initialization, lending, returning, destroying and other operations of the object in it.
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("已经销毁了所有对象"); } }
The code is relatively simple, just a simple example. Below we demonstrate how to use it by pooling a Redis connection object Jedis.
In fact, Jedis already has the corresponding Jedis pool management object JedisPool. However, in order to demonstrate the implementation of the object pool, we will not use the officially provided JedisPool.
Starting a Redis service is not introduced here. Assuming that you already have a Redis service, the following introduces the Maven dependencies needed to connect to Redis in Java.
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.2.0</version> </dependency>
How to use Jedis objects under normal circumstances:
Jedis jedis = new Jedis("localhost", 6379); String name = jedis.get("name"); System.out.println(name); jedis.close();
If you use the above object pool, you can use it as follows.
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(); } }
Output log:
Added object: 1556956098
Added object: 1252585652
Borrowed object: 1252585652
redis get:www.wdbyte .com
Returned objects: 1252585652
All objects have been destroyed
There are no objects that can be lent
If you use JMH to perform a Redis query using object pooling, and If you compare the performance of creating a Redis connection normally and then querying to close the connection, you will find that the performance of the two is very different. The following are the test results. It can be found that the performance after using object pooling is about 5 times that of the non-pooling method.
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 类的继承关系图也可以发现。
The above is the detailed content of How to implement object pool in Java. For more information, please follow other related articles on the PHP Chinese website!