什么是池化技术
池化技术是一种很常见的编程技巧,目的在于提前创建如内存,对象,线程资源,降低程序运行时频繁的创建销毁带来的开销。常见的有线程池,内存池,对象池等。
池化技术如何提高性能,降低系统开销
在实际的应用中,程序经常需要创建对象,线程和分配内存。这些设计到系统调用,系统调用会导致程序从用户态到内核态的切换会比较耗时。有了池化技术,需要用到对应的线程,对象和内存时不需要再切换上下文之需要从相应的池中获取,也不需要销毁,用完归还即可。
线程池实现的原理
先启动若干线程让其处于休眠状态/当用户中的系统需要访问时,从线程池中拿出一个已建立的空闲的连接,使用完毕后不关闭连接而是归还到线程池中。
数据库连接池的重要配置:最大连接数和最小连接数
线程池的重要配置:核心线程数,最大线程数
对象池
写一个对象池需要考虑的:
- 对象池的结构 ,应该是维护两个固定大小的阻塞队列,两个队列一个是空闲一个使用。从空闲队列中获取对象后放入使用队列。使用完对象后不进行销毁而是放入空闲队列中 .
- 池的大小 ,池中最大放多少,当池满后怎么解决 .
- 池子怎么解决并发问题防止对一个对象的竞争
一 自定义实现对象池
示例
1 对象池接口
public interface HousePool {
// 创建
House getHouse();
// 归还
void release(House po);
//初始化
void init();
}
具体实现
@Service
public class HousePoolImpl implements HousePool{
// 维护两个池子
private LinkedBlockingQueue<House> idle ;
private LinkedBlockingQueue<House> busy ;
//维护池中的对象数量
private final int maxSize = 10 ;
private AtomicInteger busySize = new AtomicInteger(0) ;
@Override
public void init() {
idle = new LinkedBlockingQueue<>();
busy = new LinkedBlockingQueue<>();
}
@Override
public House getHouse() {
// 创建对象池
House po = null;
po = idle.poll();
// 有空闲对象 直接用
if(po != null){
busy.offer(po);
return po;
}
// 未满 创建
if(busySize.get() < maxSize){
if(busySize.incrementAndGet() <= maxSize){
System.out.println("创建House");
po = new House();
busy.offer(po);
return po;
}
}
// 已满等待或超时报错
try {
idle.poll(10000, TimeUnit.MILLISECONDS);
if(po ==null){
throw new RuntimeException("超时");
}
busy.offer(po);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return po;
}
@Override
public void release(House po) {
busy.remove(po);
idle.offer(po);
}
}
初始化配置
@Configuration
public class PoolConfiguration {
@Bean
public HousePool housePool(){
HousePoolImpl housePool = new HousePoolImpl();
housePool.init();
return housePool;
}
}
代码解读:
为什么使用LinkedBlockingQueue作为对象池化的容器
1 容量可选:可以选择指定容量,也可以不指定,不指定时容量为Integer.MAX_VALUE。
2 公平性选择:可以选择是否公平地对等待的生产者和消费者进行排序。
3 阻塞操作:当队列为空时,消费者会被阻塞直到队列非空;当队列满时,生产者会被阻塞直到队列有空间。
4 线程安全。
如何解决多线程环境下对同一个空闲对象的竞争
private AtomicInteger busySize = new AtomicInteger(0) ;
if(busySize.get() < maxSize){
if(busySize.incrementAndGet() <= maxSize){
System.out.println("创建House");
po = new House();
busy.offer(po);
return po;
}
}
解决池满后等待问题
// 已满等待或超时报错
try {
idle.poll(10000, TimeUnit.MILLISECONDS);
if(po ==null){
throw new RuntimeException("超时");
}
busy.offer(po);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
测试
@Test
void testOrigin() {
housePool.init();
int i = 0 ;
while(i < 10){
House house = housePool.getHouse();
System.out.println("得到hosue");
housePool.release(house);
i++;
}
}
结果
可以看到对象只创建了一次,被使用了多次 。
二 使用commons-pool2实现对象池
commons-pool2是Apache Commons项目中的一个子项目,它提供了对象池化的实现,用于管理对象的创建、借出、归还和销毁等操作,以提高对象的重用率和系统性能。
commons-pool2主要包含以下特点:
- 通用性:可以用于池化各种类型的对象,如数据库连接、线程、Socket连接等。
- 配置灵活:可以通过配置参数来控制池的大小、对象的生命周期、借出和归还的行为等。
- 线程安全:提供了线程安全的对象池实现,适用于多线程环境。
- 监控和管理:提供了监控池状态、对象状态的功能,方便管理和调优。
通过使用commons-pool2,可以避免频繁地创建和销毁对象,提高系统的性能和资源利用率.
1 导入依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2 实现PooledObjectFactory接口
public class HousePoolFactory implements PooledObjectFactory<House> {
private static int count = 1 ;
@Override
public void activateObject(PooledObject<House> pooledObject) throws Exception {
}
@Override
public void destroyObject(PooledObject<House> pooledObject) throws Exception {
}
/*
生成对象
*/
@Override
public PooledObject<House> makeObject() throws Exception {
House house = new House(count++,"北京") ;
System.out.println("创建一个对象"+house);
return new DefaultPooledObject<>(house); // 封装成池化对象类型
}
/**
* passivateObject(PooledObject<T> p):钝化对象,
* 当对象归还到池中时调用,用于重置对象状态以便下次被借出时重新初始化
* @throws Exception
*/
@Override
public void passivateObject(PooledObject<House> pooledObject) throws Exception {
// 重置对象 .. 自定义重置方法
pooledObject.getObject().init();
}
/**
* 验证对象,用于检查对象是否仍然有效,如果对象无效则应该从池中移除。
* @param pooledObject a {@code PooledObject} wrapping the instance to be validated
*
* @return
*/
@Override
public boolean validateObject(PooledObject<House> pooledObject) {
return true;
}
}
接口方法说明
方法 | 说明 |
---|---|
activateObject(PooledObject p) | 激活对象,当对象从池中借出时调用,用于准备对象以便使用 |
destroyObject(PooledObject p) | 销毁对象,当对象从池中移除时调用,用于释放对象占用的资源 |
PooledObject makeObject() | :创建对象,当需要新的对象实例时调用,用于创建新的对象 |
passivateObject(PooledObject p) | 钝化对象,当对象归还到池中时调用,用于重置对象状态以便下次被借出时重新初始化 |
validateObject(PooledObject p) | 验证对象,用于检查对象是否仍然有效,如果对象无效则应该从池中移除 |
3 对象池的配置和使用
@Configuration
public class ObjectPoolConfig {
public GenericObjectPool<House> genericObjectPool ;
@PostConstruct
public void init() {
System.out.println("初始化对象池...");
HousePoolFactory factory = new HousePoolFactory();
//设置对象池的相关参数
GenericObjectPoolConfig poolConfig = initConfig();
//新建一个对象池,传入对象工厂和配置
genericObjectPool = new GenericObjectPool<>(factory, poolConfig);
}
/**
\* 初始化配置
*
\* @param
*/
public GenericObjectPoolConfig initConfig() {
GenericObjectPoolConfig cfg = new GenericObjectPoolConfig();
cfg.setJmxNamePrefix("objectPool");
// 对象总数
cfg.setMaxTotal(10);
// 最大空闲对象数
cfg.setMaxIdle(2);
// 最小空闲对象数
cfg.setMinIdle(2);
// 借对象阻塞最大等待时间
// 获取资源的等待时间。blockWhenExhausted 为 true 时有效。-1 代表无时间限制,一直阻塞直到有可用的资源
cfg.setMaxWaitMillis(1000l);
// 最小驱逐空闲时间
cfg.setMinEvictableIdleTimeMillis(200l);
// 每次驱逐数量 资源回收线程执行一次回收操作,回收资源的数量。默认 3
cfg.setNumTestsPerEvictionRun(3);
// 回收资源线程的执行周期,默认 -1 表示不启用回收资源线程
cfg.setTimeBetweenEvictionRunsMillis(100l);
// 资源耗尽时,是否阻塞等待获取资源,默认 true
cfg.setBlockWhenExhausted(true);
return cfg;
}
}
GenericObjectPool 类
GenericObjectPool 是一个通用对象池框架,我们可以借助它实现一个健壮的对象池,UML图如下所示:
在上面我设置了一个对象总数最大值为10并且最长等待时间为1s的对象池
测试 1: 每次都归还对象
@Test
void testOrigin() throws Exception {
GenericObjectPool<House> pool = objectPoolConfig.genericObjectPool;
for (int i = 0; i < 10; i++) {
House borrowObject = pool.borrowObject(); // 获取
System.out.println(borrowObject);
pool.returnObject(borrowObject); // 归还
}
}
测试2 :不归还对象 所需对象超过池中对象
结果:等待超时后报错
随便说说取对象的borrowObject()方法
代码看起来很复杂 简单来讲就是对象池维护了一个LinkedList,每次取的时候先从中拿到第一个对象,如果没有的话则进行创建,创建的create()方法实则是调用了我们重载后的PooledObjectFactory的makeObject方法。
while (p == null) {
create = false;
p = idleObjects.pollFirst(); // 尝试从池中获取对象
if (p == null) {
p = create(); // 创建对象
if (p != null) {
create = true;
}
}
if (blockWhenExhausted) { // 根据配置对资源进行等待
if (p == null) {
if (borrowMaxWaitDuration.isNegative()) {
p = idleObjects.takeFirst();
} else {
p = idleObjects.pollFirst(borrowMaxWaitDuration);
}
}
if (p == null) {
throw new NoSuchElementException(appendStats(
"Timeout waiting for idle object, borrowMaxWaitDuration=" + borrowMaxWaitDuration));
}
} else if (p == null) {
throw new NoSuchElementException(appendStats("Pool exhausted"));
}
if (!p.allocate()) {
p = null;
}
if (p != null) {
try {
factory.activateObject(p); // 激活对象
} catch (final Exception e) {
try {
destroy(p, DestroyMode.NORMAL); // 销毁对象 改变对象状态
} catch (final Exception e1) {
// Ignore - activation failure is more important
}
p = null;
if (create) {
final NoSuchElementException nsee = new NoSuchElementException(
appendStats("Unable to activate object"));
nsee.initCause(e);
throw nsee;
}
}
if (p != null && getTestOnBorrow()) {
boolean validate = false;
Throwable validationThrowable = null;
try {
validate = factory.validateObject(p); // 验证对象是否有效
} catch (final Throwable t) {
PoolUtils.checkRethrow(t);
validationThrowable = t;
}
if (!validate) { // 对象无效 销毁对象
try {
destroy(p, DestroyMode.NORMAL);
destroyedByBorrowValidationCount.incrementAndGet();
} catch (final Exception e) {
// Ignore - validation failure is more important
}
p = null;
if (create) {
final NoSuchElementException nsee = new NoSuchElementException(
appendStats("Unable to validate object"));
nsee.initCause(validationThrowable);
throw nsee;
}
}
}
}
}
return p.getObject();
p = create()
核心代码
try {
p = factory.makeObject();
if (getTestOnCreate() && !factory.validateObject(p)) {
createCount.decrementAndGet();
return null;
}
} catch (final Throwable e) {
createCount.decrementAndGet();
throw e;
} finally {
synchronized (makeObjectCountLock) {
makeObjectCount--;
makeObjectCountLock.notifyAll();
}
}