直接用ArrayList做对象池会出问题,因其非线程安全,多线程调用add/remove易引发IndexOutOfBoundsException、元素丢失或ConcurrentModificationException;加锁又影响性能。

为什么直接用 ArrayList 做对象池会出问题
多个线程同时调用 pool.add(obj) 或 pool.remove(0) 时,ArrayList 的非原子操作会导致 IndexOutOfBoundsException、元素丢失或 ConcurrentModificationException。它不是线程安全容器,加锁粒度粗(比如整个方法 synchronized)又容易成为性能瓶颈。
用 ConcurrentLinkedQueue 实现无锁对象池
这是 JDK 自带的无锁线程安全队列,适合“生产-消费”模式的对象复用场景。它基于 CAS,吞吐量高,且不阻塞线程。
-
poll()和offer()都是常数时间、线程安全操作,无需额外同步 - 注意:它不保证公平性,也不能按索引访问,所以只适用于“取任意一个可用对象”的池化逻辑
- 避免在池中存放强引用的大型对象(如未清理的
ByteBuffer),防止内存泄漏
public class ObjectPool{ private final ConcurrentLinkedQueue pool; private final Supplier factory; public ObjectPool(Supplier factory) { this.pool = new ConcurrentLinkedQueue<>(); this.factory = factory; } public T acquire() { T obj = pool.poll(); return (obj != null) ? obj : factory.get(); } public void release(T obj) { if (obj != null) pool.offer(obj); } }
需要阻塞等待时选 BlockingQueue 子类
当业务要求“池空时等待新对象创建完成”,就不能用 ConcurrentLinkedQueue(它返回 null),而应选 ArrayBlockingQueue 或 LinkedBlockingQueue。
-
ArrayBlockingQueue是有界、基于数组、可选公平锁,适合资源明确上限的场景(如数据库连接池) -
LinkedBlockingQueue默认无界(慎用!可能 OOM),但构造时传入容量后行为更可控 - 调用
take()会阻塞,poll(timeout, unit)可设超时,避免无限等待
// 示例:带超时的获取
public T acquire(long timeout, TimeUnit unit) throws InterruptedException {
T obj = pool.poll(timeout, unit);
return (obj != null) ? obj : factory.get();
}
对象重置逻辑必须由使用者显式控制
Java 对象池本身不关心对象状态。如果复用的 StringBuilder 或自定义 DTO 没有重置内部字段,下次使用就会携带脏数据。
立即学习“Java免费学习笔记(深入)”;
- 不要在
release()里做重置——释放方未必知道如何清空该对象 - 推荐在
acquire()返回前调用统一的reset()方法(需约定接口,如Resettable) - 若对象类型不确定,可在工厂
Supplier中封装初始化逻辑,而非依赖池内对象自带状态
真正难的不是“怎么放进去”,而是“怎么确保拿出来就能用”。多数并发问题都发生在 reset 不彻底、或重置和使用之间存在竞态——这点比选择哪种队列更值得花时间 review。










