This commit is contained in:
Looly 2023-12-18 00:59:13 +08:00
parent 1eacd29d99
commit a7abbf5402
8 changed files with 432 additions and 40 deletions

View File

@ -22,23 +22,62 @@ import java.io.Serializable;
* <li>{@link #returnObject(Poolable)}对象归还</li>
* </ul>
*
* 对于对象池中对象维护通过{@link PoolConfig#getMaxIdle()}控制规则如下
* <ul>
* <li>如果借出量很多则不断扩容直到达到{@link PoolConfig#getMaxSize()}</li>
* <li>如果池对象闲置超出{@link PoolConfig#getMaxIdle()}则销毁</li>
* <li>实际使用中池中对象可能少于{@link PoolConfig#getMinSize()}</li>
* </ul>
*
* @param <T> 对象类型
* @author looly
* @author Looly
*/
public interface ObjectPool<T> extends Closeable, Serializable {
/**
* 借出对象
* 借出对象流程如下
* <ol>
* <li>从池中取出对象</li>
* <li>检查对象可用性</li>
* <li>如果无可用对象扩容池并创建新对象</li>
* <li>继续取对象</li>
* </ol>
*
* @return 对象
*/
Poolable<T> borrowObject();
/**
* 归还对象
* 归还对象流程如下
* <ol>
* <li>检查对象可用性</li>
* <li>不可用则销毁之</li>
* <li>可用则入池</li>
* </ol>
*
* @param obj 对象
* @return this
*/
ObjectPool<T> returnObject(final Poolable<T> obj);
/**
* 获取持有对象总数包括空闲对象 + 正在使用对象数
*
* @return 总数
*/
int getTotal();
/**
* 获取空闲对象数即在池中的对象数
*
* @return 空闲对象数-1表示此信息不可用
*/
int getIdleCount();
/**
* 获取已经借出的对象正在使用的对象数
*
* @return 正在使用的对象数-1表示此对象不可用
*/
int getActiveCount();
}

View File

@ -12,54 +12,124 @@
package org.dromara.hutool.core.pool;
import java.io.Serializable;
/**
* 对象池配置提供基本的配置项包括
* <ul>
* <li>最小池大小初始大小</li>
* <li>最大池大小</li>
* <li>最大池大小</li>
* <li>最长等待时间</li>
* <li>最长空闲时间</li>
* </ul>
*
* @author Daniel, Looly
*/
public class PoolConfig {
private int minSize;
private int maxSize;
public class PoolConfig implements Serializable {
private static final long serialVersionUID = 1L;
private long maxWait;
/**
* 创建{@code PoolConfig}
*
* @return {@code PoolConfig}
*/
public static PoolConfig of() {
return new PoolConfig();
}
/**
* 最小初始池大小
*/
private int minSize = 5;
/**
* 最大池大小
*/
private int maxSize = 20;
/**
* 最长等待时间用于在借出对象时等待最长时间默认5秒
*/
private long maxWait = 5000;
/**
* 最长空闲时间在池中时间
*/
private long maxIdle;
/**
* 获取最小初始池大小
*
* @return 最小初始池大小
*/
public int getMinSize() {
return minSize;
}
/**
* 设置最小初始池大小
*
* @param minSize 最小初始池大小
* @return this
*/
public PoolConfig setMinSize(final int minSize) {
this.minSize = minSize;
return this;
}
/**
* 获取最大池大小
*
* @return 最大池大小
*/
public int getMaxSize() {
return maxSize;
}
/**
* 设置最大池大小
*
* @param maxSize 最大池大小
* @return this
*/
public PoolConfig setMaxSize(final int maxSize) {
this.maxSize = maxSize;
return this;
}
/**
* 获取最长等待时间用于在借出对象时等待最长时间
*
* @return 最长等待时间用于在借出对象时等待最长时间
*/
public long getMaxWait() {
return maxWait;
}
/**
* 设置最长等待时间用于在借出对象时等待最长时间
*
* @param maxWait 最长等待时间用于在借出对象时等待最长时间
* @return this
*/
public PoolConfig setMaxWait(final long maxWait) {
this.maxWait = maxWait;
return this;
}
/**
* 获取最长空闲时间在池中时间
*
* @return 最长空闲时间在池中时间,小于等于0表示不限制
*/
public long getMaxIdle() {
return maxIdle;
}
/**
* 设置最长空闲时间在池中时间
*
* @param maxIdle 最长空闲时间在池中时间,小于等于0表示不限制
* @return this
*/
public PoolConfig setMaxIdle(final long maxIdle) {
this.maxIdle = maxIdle;
return this;

View File

@ -14,6 +14,18 @@ package org.dromara.hutool.core.pool;
import org.dromara.hutool.core.lang.wrapper.Wrapper;
/**
* 池化对象
*
* @param <T> 对象类型
* @author Looly
*/
public interface Poolable<T> extends Wrapper<T> {
void returnObject();
/**
* 获取最后借出时间
*
* @return 最后借出时间
*/
long getLastBorrow();
}

View File

@ -13,7 +13,9 @@
package org.dromara.hutool.core.pool.partition;
import org.dromara.hutool.core.io.IoUtil;
import org.dromara.hutool.core.pool.*;
import org.dromara.hutool.core.pool.ObjectFactory;
import org.dromara.hutool.core.pool.ObjectPool;
import org.dromara.hutool.core.pool.Poolable;
import org.dromara.hutool.core.thread.ThreadUtil;
import java.io.IOException;
@ -58,6 +60,7 @@ public class PartitionObjectPool<T> implements ObjectPool<T> {
*
* @return 总数
*/
@Override
public int getTotal() {
int size = 0;
for (final PoolPartition<T> subPool : partitions) {
@ -67,16 +70,33 @@ public class PartitionObjectPool<T> implements ObjectPool<T> {
}
@Override
public Poolable<T> borrowObject() {
checkClosed();
final int partitionIndex = (int) (ThreadUtil.currentThreadId() % config.getPartitionSize());
return this.partitions[partitionIndex].borrowObject();
public int getIdleCount() {
int size = 0;
for (final PoolPartition<T> subPool : partitions) {
size += subPool.getIdleCount();
}
return size;
}
@Override
public int getActiveCount() {
int size = 0;
for (final PoolPartition<T> subPool : partitions) {
size += subPool.getActiveCount();
}
return size;
}
@Override
public Poolable<T> borrowObject() {
checkClosed();
return this.partitions[getPartitionIndex(this.config)].borrowObject();
}
@Override
public PartitionObjectPool<T> returnObject(final Poolable<T> obj) {
checkClosed();
final int partitionIndex = (int) (ThreadUtil.currentThreadId() % config.getPartitionSize());
this.partitions[partitionIndex].returnObject(obj);
this.partitions[getPartitionIndex(this.config)].returnObject(obj);
return this;
}
@ -86,10 +106,32 @@ public class PartitionObjectPool<T> implements ObjectPool<T> {
IoUtil.closeQuietly(this.partitions);
}
protected BlockingQueue<Poolable<T>> createBlockingQueue(final PoolConfig poolConfig) {
/**
* 创建阻塞队列默认为{@link ArrayBlockingQueue}<br>
* 如果需要自定义队列类型子类重写此方法
*
* @param poolConfig 池配置
* @return 队列
*/
protected BlockingQueue<PartitionPoolable<T>> createBlockingQueue(final PartitionPoolConfig poolConfig) {
return new ArrayBlockingQueue<>(poolConfig.getMaxSize());
}
/**
* 获取当前线程被分配的分区<br>
* 默认根据线程IDTID取分区大小余数<br>
* 如果需要自定义子类重写此方法
*
* @param poolConfig 池配置
* @return 分配的分区
*/
protected int getPartitionIndex(final PartitionPoolConfig poolConfig) {
return (int) (ThreadUtil.currentThreadId() % poolConfig.getPartitionSize());
}
/**
* 检查池是否关闭
*/
private void checkClosed() {
if (this.closed) {
throw new IllegalStateException("Object Pool is closed!");

View File

@ -20,12 +20,34 @@ import org.dromara.hutool.core.pool.PoolConfig;
* @author looly
*/
public class PartitionPoolConfig extends PoolConfig {
private int partitionSize;
private static final long serialVersionUID = 1L;
/**
* 创建{@code PartitionPoolConfig}
*
* @return {@code PartitionPoolConfig}
*/
public static PartitionPoolConfig of() {
return new PartitionPoolConfig();
}
private int partitionSize = 4;
/**
* 获取分区大小
*
* @return 分区大小
*/
public int getPartitionSize() {
return partitionSize;
}
/**
* 设置分区大小
*
* @param partitionSize 分区大小
* @return this
*/
public PartitionPoolConfig setPartitionSize(final int partitionSize) {
this.partitionSize = partitionSize;
return this;

View File

@ -14,14 +14,27 @@ package org.dromara.hutool.core.pool.partition;
import org.dromara.hutool.core.pool.Poolable;
/**
* 分区可池化对象此对象会同时持有原始对象和所在的分区
*
* @param <T> 对象类型
*/
public class PartitionPoolable<T> implements Poolable<T> {
private final T raw;
private final PoolPartition<T> partition;
private long lastBorrow;
public PartitionPoolable(final T raw, PoolPartition<T> partition) {
/**
* 构造
*
* @param raw 原始对象
* @param partition 对象所在分区
*/
public PartitionPoolable(final T raw, final PoolPartition<T> partition) {
this.raw = raw;
this.partition = partition;
this.lastBorrow = System.currentTimeMillis();
}
@Override
@ -29,8 +42,24 @@ public class PartitionPoolable<T> implements Poolable<T> {
return this.raw;
}
@Override
/**
* 归还对象
*/
public void returnObject() {
this.partition.returnObject(this);
}
@Override
public long getLastBorrow() {
return lastBorrow;
}
/**
* 设置最后借出时间在成功借出此对象时更新时间
*
* @param lastBorrow 最后借出时间
*/
protected void setLastBorrow(final long lastBorrow) {
this.lastBorrow = lastBorrow;
}
}

View File

@ -14,24 +14,52 @@ package org.dromara.hutool.core.pool.partition;
import org.dromara.hutool.core.exception.HutoolException;
import org.dromara.hutool.core.pool.ObjectFactory;
import org.dromara.hutool.core.pool.ObjectPool;
import org.dromara.hutool.core.pool.PoolConfig;
import org.dromara.hutool.core.pool.Poolable;
import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class PoolPartition<T> implements Closeable {
/**
* 对象池分区<br>
* 一个分区实际为一个小的对象池持有一个阻塞队列<br>
* 初始化时创建{@link PoolConfig#getMinSize()}个对象作为初始池对象.
*
* <p>
* 当借出对象时从队列头部取出并验证验证通过后使用验证不通过直接调用{@link #free(Poolable)} 销毁并重新获取
* 当池中对象都被借出空了创建新的对象并入队列直到队列满为止当满时等待归还超时则报错
* </p>
*
* <p>
* 当归还对象时验证对象不可用销毁之可用入队列
* </p>
*
* <p>
* 一个分区队列的实际
* </p>
*
* @param <T> 对象类型
*/
public class PoolPartition<T> implements ObjectPool<T> {
private static final long serialVersionUID = 1L;
private final PoolConfig config;
private final ObjectFactory<T> objectFactory;
private BlockingQueue<Poolable<T>> queue;
private BlockingQueue<PartitionPoolable<T>> queue;
// 记录对象总数包括借出对象
private int total;
public PoolPartition(PoolConfig config, BlockingQueue<Poolable<T>> queue, ObjectFactory<T> objectFactory) {
/**
* 构造
*
* @param config 池配置
* @param queue 阻塞队列类型
* @param objectFactory 对象工厂用于管理对象创建检查和销毁
*/
public PoolPartition(final PoolConfig config, final BlockingQueue<PartitionPoolable<T>> queue, final ObjectFactory<T> objectFactory) {
this.config = config;
this.queue = queue;
this.objectFactory = objectFactory;
@ -43,11 +71,26 @@ public class PoolPartition<T> implements Closeable {
total = minSize;
}
public Poolable<T> borrowObject() {
@SuppressWarnings("resource")
@Override
public PartitionPoolable<T> borrowObject() {
// 非阻塞获取
Poolable<T> poolable = this.queue.poll();
PartitionPoolable<T> poolable = this.queue.poll();
if (null != poolable) {
return poolable;
// 检查对象是否可用
if (this.objectFactory.validate(poolable.getRaw())) {
// 检查是否超过最长空闲时间
final long maxIdle = this.config.getMaxIdle();
if (maxIdle > 0 && (System.currentTimeMillis() - poolable.getLastBorrow()) <= maxIdle) {
poolable.setLastBorrow(System.currentTimeMillis());
return poolable;
}
}
// 对象不可用销毁之
free(poolable);
// 继续借而不扩容
return borrowObject();
}
// 扩容
@ -61,24 +104,41 @@ public class PoolPartition<T> implements Closeable {
}
// 扩容成功继续借对象
// 如果线程1扩容但是被线程2借走则继续递归扩容获取对象直到获取到或全部借走为止
return borrowObject();
}
/**
* 归还对象
*
* @param obj 归还的对象
* @param poolable 归还的对象
* @return this
*/
public PoolPartition<T> returnObject(final Poolable<T> obj) {
try {
this.queue.put(obj);
} catch (InterruptedException e) {
throw new HutoolException(e);
@SuppressWarnings("resource")
@Override
public PoolPartition<T> returnObject(final Poolable<T> poolable) {
// 检查对象可用性
if (this.objectFactory.validate(poolable.getRaw())) {
try {
this.queue.put((PartitionPoolable<T>) poolable);
} catch (final InterruptedException e) {
throw new HutoolException(e);
}
} else {
// 对象不可用
free(poolable);
}
return this;
}
/**
* 扩容并填充对象池队列<br>
* 如果传入的扩容大小大于可用大小即扩容大小加现有大小大于最大大小则实际扩容到最大
*
* @param increaseSize 扩容大小
* @return 实际扩容大小0表示已经达到最大未成功扩容
*/
public synchronized int increase(int increaseSize) {
if (increaseSize + total > config.getMaxSize()) {
increaseSize = config.getMaxSize() - total;
@ -107,15 +167,21 @@ public class PoolPartition<T> implements Closeable {
return this;
}
/**
* 获取对象总数包括借出对象数
*
* @return 对象数
*/
@Override
public int getTotal() {
return this.total;
}
@Override
public int getIdleCount() {
return this.queue.size();
}
@Override
public int getActiveCount() {
return getTotal() - getIdleCount();
}
@Override
public void close() throws IOException {
this.queue.forEach(this::free);
@ -123,11 +189,23 @@ public class PoolPartition<T> implements Closeable {
this.queue = null;
}
protected Poolable<T> createPoolable() {
/**
* 创建{@link PartitionPoolable}
*
* @return {@link PartitionPoolable}
*/
protected PartitionPoolable<T> createPoolable() {
return new PartitionPoolable<>(objectFactory.create(), this);
}
private Poolable<T> waitingPoll() {
/**
* 从队列中取出头部的对象如果队列为空则等待<br>
* 等待的时间取决于{@link PoolConfig#getMaxWait()}小于等于0时一直等待否则等待给定毫秒数
*
* @return 取出的池对象
* @throws HutoolException 中断异常
*/
private PartitionPoolable<T> waitingPoll() throws HutoolException {
final long maxWait = this.config.getMaxWait();
try {
if (maxWait <= 0) {

View File

@ -0,0 +1,100 @@
/*
* Copyright (c) 2023. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.pool;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.io.IoUtil;
import org.dromara.hutool.core.pool.partition.PartitionObjectPool;
import org.dromara.hutool.core.pool.partition.PartitionPoolConfig;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
public class PartitionObjectPoolTest {
public ObjectPool<StringBuilder> init() {
final PartitionPoolConfig config = (PartitionPoolConfig) PartitionPoolConfig.of()
.setPartitionSize(2)
.setMinSize(2)
.setMaxSize(20)
.setMaxIdle(5000)
.setMaxWait(100);
final ObjectFactory<StringBuilder> factory = new ObjectFactory<StringBuilder>() {
@Override
public StringBuilder create() {
return new StringBuilder();
}
@Override
public void destroy(final StringBuilder o) {
}
@Override
public boolean validate(final StringBuilder o) {
return true;
}
};
return new PartitionObjectPool<>(config, factory);
}
@Test
void borrowObjectTest() {
final ObjectPool<StringBuilder> pool = init();
// 初始4个对象
Assertions.assertEquals(4, pool.getTotal());
for (int i = 0; i < 20; i++) {
final Poolable<StringBuilder> obj = pool.borrowObject();
obj.getRaw().append("hutool");
}
// 池1的2个+池2借出的20个合计22个
Assertions.assertEquals(22, pool.getTotal());
Assertions.assertEquals(20, pool.getActiveCount());
Assertions.assertEquals(2, pool.getIdleCount());
IoUtil.closeQuietly(pool);
}
@Test
void borrowAndReturnObjectTest() {
final ObjectPool<StringBuilder> pool = init();
// 初始4个对象
Assertions.assertEquals(4, pool.getTotal());
final ArrayList<Poolable<StringBuilder>> borrowed = ListUtil.of();
for (int i = 0; i < 10; i++) {
final Poolable<StringBuilder> obj = pool.borrowObject();
obj.getRaw().append("hutool");
borrowed.add(obj);
}
// 池1的2个+池2借出的10个合计12个
Assertions.assertEquals(12, pool.getTotal());
Assertions.assertEquals(10, pool.getActiveCount());
Assertions.assertEquals(2, pool.getIdleCount());
// 全部归还
for (final Poolable<StringBuilder> obj : borrowed) {
pool.returnObject(obj);
}
// 池1的2个+池2的10个合计12个
Assertions.assertEquals(12, pool.getTotal());
Assertions.assertEquals(0, pool.getActiveCount());
// 全部空闲
Assertions.assertEquals(12, pool.getIdleCount());
IoUtil.closeQuietly(pool);
}
}