java线程池,java池的概念
00-1010 1.什么是对象池2。为什么需要对象池3。对象池4的实现。开源对象池工具5。Jedispool对象池6的实现分析。对象池总结最近在分析一个应用中一个接口的耗时情况时,我们发现一个看似普通的对象创建操作,实际上每次都需要8ms左右。经过分析,我们发现这个对象可以通过对象池模式进行优化。
00-1010池化并不是一项新技术。它更像是一种软件设计模式。它的主要功能是缓存一组已初始化的对象以备不时之需。在大多数情况下,对象池缓存那些创建成本太高或者需要重复创建的对象。从池中获取对象的时间是可以预测的,但是创建新对象的时间是不确定的。
当需要一个新的对象时,将其借给池,然后对象池标记当前对象正在使用,使用后,将其返回对象池再次借出。
常见的对象池使用场景:
1.对象创建成本太高。2.需要频繁创建大量的重复对象,会产生大量的内存碎片。3.同时使用的对象不会太多。4.常见的特定场景,如数据库连接池和线程池。
目录
如果对象的创建成本很高,(如建立数据库连接)花费太长时间,我们的查询过程可能会像这样,而不使用池技术。1:建立数据库连接-发起查询-接收响应-关闭连接查询2:建立数据库连接-发起查询-接收响应-关闭连接查询3:建立数据库连接-发起查询-接收响应-关闭连接。在这种模式下,每次都必须重新建立闭合连接。因为建立连接是一项耗时的操作,所以这种模式会影响程序的整体性能。
那么用统筹的思想是什么感觉呢?相同的过程将被转换为以下步骤。
初始化:建立N个数据库连接-缓存到查询1:从缓存中借用数据库连接-发起查询-接收响应-返回数据库连接对象到缓存查询2:从缓存中借用数据库连接-发起查询-返回数据库连接对象到缓存查询3:从缓存中借用数据库连接-发起查询-接收响应-返回数据库连接对象到缓存使用了连接池的思想后,数据库连接不会频繁地被创建和关闭,而是被启动。
00-1010通过上面的例子,我们还可以发现汇集思想的几个关键步骤:初始化、借出、归还.没有上图所示的销毁步骤。在某些情况下,需要销毁对象,例如释放连接。
让我们手动实现一个简单的对象池来加深对对象池的理解。它主要定义一个对象池管理类,然后在里面实现对象的初始化、借出、归还、销毁等操作。
package com . wdbyet . tool . object pool . my pool;import Java . io . close able;import Java . io . io exception;import Java . util . hashset;import Java . util . stack;/* * * * @ author https://www.wdbyte.com */publiclassjobjectpooltextendscloseable {//池大小privateIntegersize=5;//对象池堆栈。后进先出privatestacktstackpool=new stack();lent对象privatehassettingerborrowhashcodeset=new hashset()的hashCode集合;/* * *添加一个对象* * @ paramt */public synchronized VoidadDobj(TT){ if((stack pool . size()borrohashcodeset . size())==size){ ThrownRunTimeException(已达到池中的最大对象数);} stack pool . add(t);system . out . println( add object : t . hashcode());}/* * *借出对象* * @ return */public synchronized tborrow obj(){
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借出了对象:1252585652redisget:www.wdbyte.com归还了对象:1252585652已经销毁了所有对象没有可以被借出的对象如果使用 JMH 对使用对象池化进行 Redis 查询,和正常创建 Redis 连接然后查询关闭连接的方式进行性能对比,会发现两者的性能差异很大。下面是测试结果,可以发现使用对象池化后的性能是非池化方式的 5 倍左右。
BenchmarkModeCntScoreErrorUnitsMyObjectPoolTest.testthrpt152612.689±358.767ops/sMyObjectPoolTest.testPoolthrpt912414.228±11669.484ops/s
4. 开源的对象池工具
上面自己实现的对象池总归有些简陋了,其实开源工具中已经有了非常好用的对象池的实现,如 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(); }}输出日志:
redisget: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
5. JedisPool 对象池实现分析
这篇文章中的演示都使用了 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.Poolpublic 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.Poolpublic 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 类的继承关系图也可以发现。
JedisPool 继承关系
6. 对象池总结
通过这篇文章的介绍,可以发现池化思想有几个明显的优势。1. 可以显著的提高应用程序的性能。2. 如果一个对象创建成本过高,那么使用池化非常有效。3. 池化提供了一种对象的管理以及重复使用的方式,减少内存碎片。4. 可以为对象的创建数量提供限制,对某些对象不能创建过多的场景提供保护。但是使用对象池化也有一些需要注意的地方,比如归还对象时应确保对象已经被重置为可以重复使用的状态。同时也要注意,使用池化时要根据具体的场景合理的设置池子的大小,过小达不到想要的效果,过大会造成内存浪费。
以上就是一文搞懂Java中对象池的实现的详细内容,更多关于Java对象池的资料请关注盛行IT其它相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。