对象池模式是一种强大的设计模式,可以通过重用昂贵的对象显著提高应用程序性能和效率。它提供了一种管理共享资源的机制,并通过限制创建的对象数量来防止资源耗尽。如果使用得当,对象池模式可以成为提高软件应用程序的可伸缩性和可靠性的有效工具。
前言
对象池模式是软件开发中广泛使用的设计模式,旨在通过重用创建成本高昂的对象来提高应用程序性能和效率。它在创建对象的新实例非常耗时且对象创建频率很高的情况下特别有用。当可以创建的对象实例数量由于资源限制而受到限制时,此模式也很有用。
工作机制
对象池模式的工作原理是创建一个预初始化对象池,可以根据需要借用和归还这些对象。不是每次需要时都创建一个新对象,而是在池中搜索可以重用的可用对象。如果对象可用,则将其从池中移除并返回给请求对象,否则,将创建一个新对象并将其添加到池中。
代码实现对象池
我这边通过使用Apache Common Pool来实现对象的池化技术。
- 引入依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.9.0</version>
</dependency>
- 需要池化的对象示例
public class Foo {
private final String username;
public Foo(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
}
- 构建对象创建工厂 可以直接实现org.apache.commons.pool2.PooledObjectFactory<T>接口实现创建、销毁、钝化、取消等接口,也可以使用他的抽象类,实现创建和包装方法即可。
public class FooPoolObjectFactory extends BasePooledObjectFactory<Foo> {
@Override
public Foo create() throws Exception {
return new Foo(String.valueOf(RandomUtils.randomInt(0, 10)));
}
@Override
public PooledObject<Foo> wrap(Foo obj) {
return new DefaultPooledObject<>(obj);
}
}
- 实现驱逐策略。我们有必要定期对对象的"健康状态"进行检查,剔除掉"不能用"的对象,并填充新的对象给"对象池"。一般数据库链接对象,要定期进行心跳,确保连接可用,如果连接断开,需要销毁对象,并重新创建新的对象。common-pool中,我们可以实现驱逐策略,对对象进行定期检查。
public class FooEvictionPolicy implements EvictionPolicy<Foo> {
@Override
public boolean evict(EvictionConfig config, PooledObject<Foo> underTest, int idleCount) {
// todo 定期检查对象某些功能是否可用
return true;
}
}
- 构建&配置对象池
public GenericObjectPool<Foo> fooGenericObjectPool() {
GenericObjectPoolConfig<Foo> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setEvictionPolicy(new FooEvictionPolicy());
poolConfig.setBlockWhenExhausted(true);
poolConfig.setJmxEnabled(false);
poolConfig.setMaxWaitMillis(1000 * 10);
poolConfig.setTimeBetweenEvictionRunsMillis(60 * 1000);
poolConfig.setMinEvictableIdleTimeMillis(20 * 1000);
poolConfig.setTestWhileIdle(true);
poolConfig.setTestOnReturn(true);
poolConfig.setTestOnBorrow(true);
poolConfig.setMaxTotal(3);
// 设置抛弃策略
AbandonedConfig abandonedConfig = new AbandonedConfig();
abandonedConfig.setRemoveAbandonedOnMaintenance(true);
abandonedConfig.setRemoveAbandonedOnBorrow(true);
return new GenericObjectPool<>(new FooPoolObjectFactory(), poolConfig, abandonedConfig);
}
- 获取&归还对象
private final GenericObjectPool<Foo> fooGenericObjectPool = fooGenericObjectPool();
public Foo borrowFoo () throws Exception {
return fooGenericObjectPool.borrowObject();
}
public void returnObject(Foo foo){
fooGenericObjectPool.returnObject(foo);
}
对象池优点
- 提高性能,对象池模式可以通过减少与对象创建和销毁相关的开销来显着提高应用程序的性能。通过重用预先初始化的对象,该模式减少了需要创建的对象数量,进而减少了创建新对象所需的时间和资源。
- 资源管理,对象池模式提供了一种管理共享资源的机制,例如数据库连接或文件句柄。通过限制创建的对象数量,该模式可以防止资源耗尽并确保资源得到有效共享。
- 一致性,对象池模式可以通过确保所有对象在使用前都预先初始化为已知状态来帮助确保应用程序的一致性。这在对象初始化复杂或耗时的情况下特别有用。
- 易于实现,对象池模式相对容易实现,可用于多种情况。它是一种经过验证的设计模式,已在许多应用程序和编程语言中成功使用。
对象池缺点
- 增加复杂性,对象池模式可以通过添加额外的抽象层来增加应用程序的复杂性。这会使代码更难理解和维护,尤其是在池大小和对象生命周期管理不当的情况下。
- 开销,虽然对象池模式可以通过减少与对象创建和销毁相关的开销来提高性能,但由于池本身的管理,它也会引入额外的开销。如果池大小没有针对应用程序的需要进行优化,这种开销会变得很大。
- 有限的灵活性:对象池模式旨在管理一组固定的对象,可能不适合需要动态对象创建或可变池大小的应用程序。
- 线程安全,如果多个线程同时访问池,对象池模式会引入线程安全问题。同步机制必须到位以确保一次只有一个线程可以访问池,这可能会增加额外的开销和代码的复杂性。
- 资源泄漏,如果对象没有正确返回到池中,它们可能会“泄漏”并且无法重用。随着时间的推移,这会导致资源耗尽并降低应用程序性能。
应用场景
一般需要池化的对象往往都是比"重量级"较的对象,创建和销毁都比较耗时,比如我们的线程,数据库连接对象,TCP连接对象,FTP连接对象 等等,我们来具体看几个例子把。
- Web服务器例子
Web 服务器通常需要处理大量并发请求,这会给系统资源带来巨大压力。通过使用对象池来管理数据库连接、网络套接字或其他资源,从而提高Web 服务器的性能和可扩展性,避免资源耗尽。
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class ConnectionPool {
private static final int MAX_POOL_SIZE = 10;
private static final int MAX_WAIT_TIME = 5000; // milliseconds
private static final int PORT_NUMBER = 8080;
private final BlockingQueue<Socket> pool;
private final ServerSocket serverSocket;
public ConnectionPool() throws Exception {
pool = new ArrayBlockingQueue<>(MAX_POOL_SIZE);
serverSocket = new ServerSocket(PORT_NUMBER);
System.out.println("Server started on port " + PORT_NUMBER);
}
public Socket getConnection() throws Exception {
Socket connection = pool.poll();
if (connection == null) {
try {
connection = serverSocket.accept();
System.out.println("New connection accepted from " + connection.getInetAddress());
} catch (SocketTimeoutException e) {
System.out.println("Timeout waiting for connection. No connection found within " + MAX_WAIT_TIME + " milliseconds.");
}
}
return connection;
}
public void returnConnection(Socket connection) {
if (pool.size() < MAX_POOL_SIZE) {
pool.offer(connection);
System.out.println("Connection returned to pool. Pool size is now " + pool.size());
} else {
try {
connection.close();
System.out.println("Connection pool is full. Discarded connection.");
} catch (Exception e) {
System.out.println("Error closing discarded connection.");
}
}
}
public static void main(String[] args) throws Exception {
ConnectionPool connectionPool = new ConnectionPool();
while (true) {
Socket connection = connectionPool.getConnection();
// Do some work with the connection
Thread.sleep(5000);
connectionPool.returnConnection(connection);
}
}
}
在此示例中, ConnectionPool类用于管理到 Web 服务器的网络连接池,构造函数将连接池初始化为最大 10 个连接,并在端口号 8080 上启动服务器。
调用getConnection()方法可以从池中返回一个连接对象,如果池为空,则从服务器套接字接受新连接。它最多等待 5 秒以使连接可用,然后超时并返回 null。
如果池未满,则 returnConnection ()方法将连接对象添加回池中,如果池已满,则关闭连接并丢弃它。
在 main () 方法中,创建ConnectionPool对象,并在循环中重复获取连接并返回到池中。这是对象池模式如何用于管理 Web 服务器中的连接以有效利用资源的示例。
- 游戏开发种的例子
游戏通常需要快速创建和销毁大量对象,例如粒子、子弹或敌人。通过使用对象池来管理这些对象,游戏可以提高性能并减少与对象创建和销毁相关的开销。
import java.util.ArrayList;
import java.util.List;
public class GameObjectPool {
class GameObject {
public void reset() {
// reset object to default state
}
}
private static final int MAX_POOL_SIZE = 10;
private final List<GameObject> pool;
public GameObjectPool() {
pool = new ArrayList<>(MAX_POOL_SIZE);
for (int i = 0; i < MAX_POOL_SIZE; i++) {
pool.add(new GameObject());
}
}
public GameObject getObject() {
GameObject gameObject = pool.remove(0);
gameObject.reset();
return gameObject;
}
public void returnObject(GameObject gameObject) {
if (pool.size() < MAX_POOL_SIZE) {
pool.add(gameObject);
}
}
public static void main(String[] args) {
GameObjectPool gameObjectPool = new GameObjectPool();
// Use game objects from pool
GameObject gameObject1 = gameObjectPool.getObject();
// modify gameObject1
gameObjectPool.returnObject(gameObject1);
GameObject gameObject2 = gameObjectPool.getObject();
// modify gameObject2
gameObjectPool.returnObject(gameObject2);
}
}
在此示例中,GameObjectPool类用于管理游戏开发场景中的GameObject对象池。构造函数将池初始化为最大大小 10,并创建GameObject对象来填充池。
调用getObject ()方法从池中移除一个对象,并在返回之前将其重置为默认状态。如果池未满,则 returnObject ()方法将一个对象添加回池中。
在 main ()方法中,创建 GameObjectPool对象并重复获取游戏对象并返回到池中。这是对象池模式如何用于管理游戏开发场景中的游戏对象以有效利用资源的示例。
总结
总之,对象池模式是一种强大的设计模式,可以通过重用昂贵的对象显著提高应用程序性能和效率。它提供了一种管理共享资源的机制,并通过限制创建的对象数量来防止资源耗尽。如果使用得当,对象池模式可以成为提高软件应用程序的可伸缩性和可靠性的有效工具。