diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/pool/ObjectPool.java b/hutool-core/src/main/java/org/dromara/hutool/core/pool/ObjectPool.java index 30b90b4ba..78fc33f9d 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/pool/ObjectPool.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/pool/ObjectPool.java @@ -22,23 +22,62 @@ import java.io.Serializable; *
  • {@link #returnObject(Poolable)}对象归还。
  • * * + * 对于对象池中对象维护,通过{@link PoolConfig#getMaxIdle()}控制,规则如下: + * + * * @param 对象类型 - * @author looly + * @author Looly */ public interface ObjectPool extends Closeable, Serializable { /** - * 借出对象 + * 借出对象,流程如下: + *
      + *
    1. 从池中取出对象
    2. + *
    3. 检查对象可用性
    4. + *
    5. 如果无可用对象,扩容池并创建新对象
    6. + *
    7. 继续取对象
    8. + *
    * * @return 对象 */ Poolable borrowObject(); /** - * 归还对象 + * 归还对象,流程如下: + *
      + *
    1. 检查对象可用性
    2. + *
    3. 不可用则销毁之
    4. + *
    5. 可用则入池
    6. + *
    * * @param obj 对象 * @return this */ ObjectPool returnObject(final Poolable obj); + + /** + * 获取持有对象总数(包括空闲对象 + 正在使用对象数) + * + * @return 总数 + */ + int getTotal(); + + /** + * 获取空闲对象数,即在池中的对象数 + * + * @return 空闲对象数,-1表示此信息不可用 + */ + int getIdleCount(); + + /** + * 获取已经借出的对象(正在使用的)对象数 + * + * @return 正在使用的对象数,-1表示此对象不可用 + */ + int getActiveCount(); } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/pool/PoolConfig.java b/hutool-core/src/main/java/org/dromara/hutool/core/pool/PoolConfig.java index 27f04d993..9a402b042 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/pool/PoolConfig.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/pool/PoolConfig.java @@ -12,54 +12,124 @@ package org.dromara.hutool.core.pool; +import java.io.Serializable; + /** * 对象池配置,提供基本的配置项,包括: *
      *
    • 最小池大小(初始大小)
    • *
    • 最大池大小
    • - *
    • 最大池大小
    • *
    • 最长等待时间
    • *
    • 最长空闲时间
    • *
    + * + * @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; diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/pool/Poolable.java b/hutool-core/src/main/java/org/dromara/hutool/core/pool/Poolable.java index b7acbc564..2484f3cc4 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/pool/Poolable.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/pool/Poolable.java @@ -14,6 +14,18 @@ package org.dromara.hutool.core.pool; import org.dromara.hutool.core.lang.wrapper.Wrapper; +/** + * 池化对象 + * + * @param 对象类型 + * @author Looly + */ public interface Poolable extends Wrapper { - void returnObject(); + + /** + * 获取最后借出时间 + * + * @return 最后借出时间 + */ + long getLastBorrow(); } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PartitionObjectPool.java b/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PartitionObjectPool.java index f791d8310..37eb2a4be 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PartitionObjectPool.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PartitionObjectPool.java @@ -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 implements ObjectPool { * * @return 总数 */ + @Override public int getTotal() { int size = 0; for (final PoolPartition subPool : partitions) { @@ -67,16 +70,33 @@ public class PartitionObjectPool implements ObjectPool { } @Override - public Poolable borrowObject() { - checkClosed(); - final int partitionIndex = (int) (ThreadUtil.currentThreadId() % config.getPartitionSize()); - return this.partitions[partitionIndex].borrowObject(); + public int getIdleCount() { + int size = 0; + for (final PoolPartition subPool : partitions) { + size += subPool.getIdleCount(); + } + return size; } + @Override + public int getActiveCount() { + int size = 0; + for (final PoolPartition subPool : partitions) { + size += subPool.getActiveCount(); + } + return size; + } + + @Override + public Poolable borrowObject() { + checkClosed(); + return this.partitions[getPartitionIndex(this.config)].borrowObject(); + } + + @Override public PartitionObjectPool returnObject(final Poolable 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 implements ObjectPool { IoUtil.closeQuietly(this.partitions); } - protected BlockingQueue> createBlockingQueue(final PoolConfig poolConfig) { + /** + * 创建阻塞队列,默认为{@link ArrayBlockingQueue}
    + * 如果需要自定义队列类型,子类重写此方法 + * + * @param poolConfig 池配置 + * @return 队列 + */ + protected BlockingQueue> createBlockingQueue(final PartitionPoolConfig poolConfig) { return new ArrayBlockingQueue<>(poolConfig.getMaxSize()); } + /** + * 获取当前线程被分配的分区
    + * 默认根据线程ID(TID)取分区大小余数
    + * 如果需要自定义,子类重写此方法 + * + * @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!"); diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PartitionPoolConfig.java b/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PartitionPoolConfig.java index d82e00b6e..dd9acb03f 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PartitionPoolConfig.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PartitionPoolConfig.java @@ -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; diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PartitionPoolable.java b/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PartitionPoolable.java index 13d3bbf35..37eb029c5 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PartitionPoolable.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PartitionPoolable.java @@ -14,14 +14,27 @@ package org.dromara.hutool.core.pool.partition; import org.dromara.hutool.core.pool.Poolable; +/** + * 分区可池化对象,此对象会同时持有原始对象和所在的分区 + * + * @param 对象类型 + */ public class PartitionPoolable implements Poolable { private final T raw; private final PoolPartition partition; + private long lastBorrow; - public PartitionPoolable(final T raw, PoolPartition partition) { + /** + * 构造 + * + * @param raw 原始对象 + * @param partition 对象所在分区 + */ + public PartitionPoolable(final T raw, final PoolPartition partition) { this.raw = raw; this.partition = partition; + this.lastBorrow = System.currentTimeMillis(); } @Override @@ -29,8 +42,24 @@ public class PartitionPoolable implements Poolable { 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; + } } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PoolPartition.java b/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PoolPartition.java index ee5b28d04..5de5617d6 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PoolPartition.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/pool/partition/PoolPartition.java @@ -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 implements Closeable { +/** + * 对象池分区
    + * 一个分区实际为一个小的对象池,持有一个阻塞队列。
    + * 初始化时创建{@link PoolConfig#getMinSize()}个对象作为初始池对象. + * + *

    + * 当借出对象时,从队列头部取出并验证,验证通过后使用,验证不通过直接调用{@link #free(Poolable)} 销毁并重新获取, + * 当池中对象都被借出(空了),创建新的对象并入队列,直到队列满为止,当满时等待归还,超时则报错。 + *

    + * + *

    + * 当归还对象时,验证对象,不可用销毁之,可用入队列。 + *

    + * + *

    + * 一个分区队列的实际 + *

    + * + * @param 对象类型 + */ +public class PoolPartition implements ObjectPool { + private static final long serialVersionUID = 1L; private final PoolConfig config; private final ObjectFactory objectFactory; - private BlockingQueue> queue; + private BlockingQueue> queue; // 记录对象总数(包括借出对象) private int total; - public PoolPartition(PoolConfig config, BlockingQueue> queue, ObjectFactory objectFactory) { + /** + * 构造 + * + * @param config 池配置 + * @param queue 阻塞队列类型 + * @param objectFactory 对象工厂,用于管理对象创建、检查和销毁 + */ + public PoolPartition(final PoolConfig config, final BlockingQueue> queue, final ObjectFactory objectFactory) { this.config = config; this.queue = queue; this.objectFactory = objectFactory; @@ -43,11 +71,26 @@ public class PoolPartition implements Closeable { total = minSize; } - public Poolable borrowObject() { + @SuppressWarnings("resource") + @Override + public PartitionPoolable borrowObject() { // 非阻塞获取 - Poolable poolable = this.queue.poll(); + PartitionPoolable 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 implements Closeable { } // 扩容成功,继续借对象 + // 如果线程1扩容,但是被线程2借走,则继续递归扩容获取对象,直到获取到或全部借走为止 return borrowObject(); } /** * 归还对象 * - * @param obj 归还的对象 + * @param poolable 归还的对象 * @return this */ - public PoolPartition returnObject(final Poolable obj) { - try { - this.queue.put(obj); - } catch (InterruptedException e) { - throw new HutoolException(e); + @SuppressWarnings("resource") + @Override + public PoolPartition returnObject(final Poolable poolable) { + // 检查对象可用性 + if (this.objectFactory.validate(poolable.getRaw())) { + try { + this.queue.put((PartitionPoolable) poolable); + } catch (final InterruptedException e) { + throw new HutoolException(e); + } + } else { + // 对象不可用 + free(poolable); } + return this; } + /** + * 扩容并填充对象池队列
    + * 如果传入的扩容大小大于可用大小(即扩容大小加现有大小大于最大大小,则实际扩容到最大) + * + * @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 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 implements Closeable { this.queue = null; } - protected Poolable createPoolable() { + /** + * 创建{@link PartitionPoolable} + * + * @return {@link PartitionPoolable} + */ + protected PartitionPoolable createPoolable() { return new PartitionPoolable<>(objectFactory.create(), this); } - private Poolable waitingPoll() { + /** + * 从队列中取出头部的对象,如果队列为空,则等待
    + * 等待的时间取决于{@link PoolConfig#getMaxWait()},小于等于0时一直等待,否则等待给定毫秒数 + * + * @return 取出的池对象 + * @throws HutoolException 中断异常 + */ + private PartitionPoolable waitingPoll() throws HutoolException { final long maxWait = this.config.getMaxWait(); try { if (maxWait <= 0) { diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/pool/PartitionObjectPoolTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/pool/PartitionObjectPoolTest.java new file mode 100644 index 000000000..6135465f1 --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/pool/PartitionObjectPoolTest.java @@ -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 init() { + final PartitionPoolConfig config = (PartitionPoolConfig) PartitionPoolConfig.of() + .setPartitionSize(2) + .setMinSize(2) + .setMaxSize(20) + .setMaxIdle(5000) + .setMaxWait(100); + + final ObjectFactory factory = new ObjectFactory() { + @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 pool = init(); + // 初始4个对象 + Assertions.assertEquals(4, pool.getTotal()); + + for (int i = 0; i < 20; i++) { + final Poolable 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 pool = init(); + // 初始4个对象 + Assertions.assertEquals(4, pool.getTotal()); + + final ArrayList> borrowed = ListUtil.of(); + for (int i = 0; i < 10; i++) { + final Poolable 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 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); + } +}