秒杀场景下,涉及到短时间大量请求,虽然最终落到数据库的请求很少,但这过程会涉及到许多临时对象的创建(如订单请求DTO、库存DTO),导致频繁youngGC。Young GC在Stop-The-World(STW)期间,JVM必须暂停所有线程,会导致请求延迟飙升,违背秒杀系统的低延迟要求。因此需要引入对象池化技术。
1. 简单的对象池
1.1 SimpleObjectPool
这是一个简单的对象池模型,使用BlockingQueue存放对象,在使用完成后将对象丢回池中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
| import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
public class SimpleObjectPool<T> { private final BlockingQueue<T> pool;
private final ObjectFactory<T> factory;
public SimpleObjectPool(int initialSize, int maxSize, ObjectFactory<T> factory) { this.factory = factory; this.pool = new LinkedBlockingQueue<>(maxSize); initializePool(initialSize, factory); }
private void initializePool(int initialSize, ObjectFactory<T> factory) { for (int i = 0; i < initialSize; i++) { pool.add(factory.create()); } }
public T borrowObject() throws InterruptedException { return pool.take(); }
public void returnObject(T object) { if (object != null) { pool.offer(object); } }
public interface ObjectFactory<T> { T create(); }
static class ExpensiveObject { private static int count = 0; private final int id;
public ExpensiveObject() { this.id = ++count; System.out.println("创建对象: ExpensiveObject-" + id); }
public void doSomething() { System.out.println(Thread.currentThread().getName() + " 获取到对象: ExpensiveObject-" + id); }
public String getName(){ return "ExpensiveObject-" + id; } }
public static void main(String[] args) {
SimpleObjectPool<ExpensiveObject> pool = new SimpleObjectPool<>( 2, 3, ExpensiveObject::new );
Runnable task = () -> { try { String threadName = Thread.currentThread().getName(); System.out.println(threadName + " 尝试获取对象"); ExpensiveObject obj = pool.borrowObject(); obj.doSomething(); Thread.sleep(3000); pool.returnObject(obj); System.out.println(threadName + " 归还对象:" + obj.getName()); } catch (InterruptedException e) { e.printStackTrace(); } };
for (int i = 0; i < 5; i++) { new Thread(task).start(); } } }
|
1.2 执行结果
执行main方法,运行结果如下。可以看出,总共只创建了2个对象,后面线程要等待前面线程归还后才能获得对象。

1.3 引伸-函数式接口
SimpleObjectPool构造方法需要一个ObjectFactory对象
1 2 3 4 5
| public SimpleObjectPool(int initialSize, int maxSize, ObjectFactory<T> factory) { this.factory = factory; this.pool = new LinkedBlockingQueue<>(maxSize); initializePool(initialSize, factory); }
|
但是传入的却是一个方法引用
1 2 3
| SimpleObjectPool<ExpensiveObject> pool = new SimpleObjectPool<>( 2, 3, ExpensiveObject::new );
|
ExpensiveObject::new表示 引用 ExpensiveObject 的无参构造方法,等价于
1
| () -> new ExpensiveObject()
|
Java 允许用 Lambda 表达式 或 方法引用 来替代函数式接口的实现。ExpensiveObject::new 正好匹配 ObjectFactory.create() 的签名(无参,返回 ExpensiveObject 对象),所以可以直接使用。
另外,函数式接口是指 仅包含一个抽象方法(SAM,Single Abstract Method)的接口,可以用 @FunctionalInterface 注解标记(非强制,但推荐用于编译时检查)
2. Apache Commons Pool2
Apache Commons Pool2 是一个对象池化框架,用于管理对象池,可以有效地重用对象,减少创建和销毁对象的开销。
2.1. 基本概念
- PooledObject: 池中管理的对象
- ObjectPool: 对象池接口
- PooledObjectFactory: 对象工厂,负责创建、销毁和验证对象
- GenericObjectPool: 最常用的对象池实现
2.2. 快速开始
2.2.1 添加依赖
1 2 3 4 5
| <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.11.1</version> </dependency>
|
2.2.2 基本使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| PooledObjectFactory<MyObject> factory = new BasePooledObjectFactory<MyObject>() { @Override public MyObject create() throws Exception { return new MyObject(); } @Override public PooledObject<MyObject> wrap(MyObject obj) { return new DefaultPooledObject<>(obj); } };
ObjectPool<MyObject> pool = new GenericObjectPool<>(factory);
try { MyObject obj = pool.borrowObject(); try { obj.doSomething(); } finally { pool.returnObject(obj); } } catch (Exception e) { e.printStackTrace(); }
|
2.3. 配置 GenericObjectPool
可以通过 GenericObjectPoolConfig 进行详细配置:
1 2 3 4 5 6 7 8 9 10
| GenericObjectPoolConfig<MyObject> config = new GenericObjectPoolConfig<>(); config.setMaxTotal(8); config.setMaxIdle(8); config.setMinIdle(4); config.setTestOnBorrow(true); config.setTestOnReturn(true); config.setTestWhileIdle(true); config.setTimeBetweenEvictionRunsMillis(60000);
ObjectPool<MyObject> pool = new GenericObjectPool<>(factory, config);
|
2.4. 高级特性
2.4.1 自定义对象工厂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class MyObjectFactory extends BasePooledObjectFactory<MyObject> { @Override public MyObject create() throws Exception { return new MyObject(); } @Override public PooledObject<MyObject> wrap(MyObject obj) { return new DefaultPooledObject<>(obj); } @Override public boolean validateObject(PooledObject<MyObject> p) { return p.getObject().isValid(); } @Override public void destroyObject(PooledObject<MyObject> p) throws Exception { p.getObject().close(); } @Override public void activateObject(PooledObject<MyObject> p) throws Exception { p.getObject().initialize(); } @Override public void passivateObject(PooledObject<MyObject> p) throws Exception { p.getObject().reset(); } }
|
2.4.2 使用软引用对象池
1
| SoftReferenceObjectPool<MyObject> pool = new SoftReferenceObjectPool<>(factory);
|
2.5. 最佳实践
- 总是确保归还对象:使用 try-finally 块确保对象归还
- 合理配置池大小:根据系统资源和需求调整
- 实现适当的验证:避免使用无效对象
- 监控池状态:定期检查池的使用情况
- 考虑使用连接池:对于数据库连接等资源,考虑使用专门的连接池如 HikariCP
2.6. 常见问题
2.6.1 对象泄漏
如果忘记归还对象,会导致池中对象耗尽。解决方案:
1 2 3 4 5 6
| MyObject obj = pool.borrowObject(); try { } finally { pool.returnObject(obj); }
|
2.6.2 性能调优
- 调整
maxTotal 和 maxIdle 参数
- 根据使用模式决定是否启用对象验证
- 考虑使用
Lifo (后进先出) 或 Fifo (先进先出) 策略