Java对象池技术
# 引言
# 1. 什么是对象池技术
对象池技术是一种软件设计模式,它预先创建一定数量的对象,并把这些对象放入池(Pool)中保存起来。
当程序需要对象时,不是直接创建,而是从对象池中取得已创建的空闲对象,使用完后再放回池中。这种方式可以重用存在的对象,避免频繁创建和销毁对象,从而提高系统性能。
对象池技术最重要的优点就是对象重用,以提高系统性能为目标。对象池还可以控制对象个数,实现资源的统一分配和管理。
通过设置池的最大空闲对象数和最大对象数,可以有效避免系统资源的浪费。
# 2. 对象池技术的作用
对象池技术有以下几个作用:
提高性能:通过重用对象,避免频繁创建和销毁对象的开销,从而提高系统的性能和响应速度。
资源管理:对象池可以限制对象的数量,避免资源的过度消耗,保护系统的稳定性。
对象复用:通过对象池,可以重复使用已有的对象,避免重复创建相同的对象,节省内存空间。
对象生命周期管理:对象池可以管理对象的生命周期,包括对象的初始化、激活、钝化和销毁等过程,确保对象的正确使用和释放。
对象池技术在多线程、网络编程、数据库连接等场景中得到广泛的应用,能够有效提高系统的性能和资源利用率。下面将详细介绍对象池的实现原理和Java中的对象池技术。
# 对象池的实现原理
对象池的实现原理主要包括以下几点:
初始化:在对象池初始化时,会提前创建一定数量的对象,存储在容器中待用。
获取对象:当客户端请求对象时,如果池中存在可用的对象,就直接返回该对象,而不是新建对象。
使用完对象后,客户端不销毁该对象,而是将其“归还”给对象池。
归还对象时,对象池会对该对象进行检查,确保其可被重复使用。比如清除状态、重置属性等。
如果所有对象都在使用中,客户端请求对象时,对象池会根据策略创建新的对象,具体策略可以由客户端设定,比如等待、直接创建新对象、抛出异常等。
对象池内部采用队列或者链表等结构进行对象管理,以便获取及归还对象。
对象池需要具有线程安全机制,保证多线程环境下的并发访问安全。
对象池一般会设置最大空闲对象数和最大对象数,避免对象池资源耗尽。
当系统关闭时,对象池也需要正确关闭、释放掉所有的对象。
总之,对象池提前创建对象,在需要时重复使用EXISTING对象,而不是每次都创建新对象,这样可以有效提高系统性能,减少内存的频繁分配和回收。对象池主要靠提前初始化及对象重用来实现这一目的。
# Java中的对象池技术
# 1. Java对象池的分类
在Java中,对象池技术有多种不同的分类方式。根据对象的创建和销毁方式,可以将Java对象池分为以下几类:
手动管理对象池:开发人员手动创建和管理对象池,包括对象的初始化、获取和释放等操作。这种方式灵活性较高,但需要开发人员自行管理对象的生命周期和线程安全等问题。
使用第三方库实现对象池:许多第三方库提供了成熟的对象池实现,如Apache Commons Pool、Spring Object Pool等。这些库提供了丰富的功能和配置选项,可以方便地使用对象池技术。
Java标准库提供的对象池:Java标准库中也提供了一些对象池相关的类,如线程池等。这些对象池通常是为特定的场景和需求设计的,可以方便地使用和集成。
# 2. Java对象池的常见应用场景
# a 线程池
线程池是Java中最常见的对象池之一。
在多线程编程中,频繁创建和销毁线程会消耗大量的系统资源,影响系统的性能和稳定性。通过使用线程池,可以事先创建一定数量的线程,并将其保存在池中。当需要执行任务时,可以从线程池中获取可用的线程来执行任务,执行完毕后将线程放回池中。
具体线程池的使用,请参考 Java并发-线程池ThreadPoolExecutor
# b 连接池
连接池是在数据库编程中常见的对象池技术。
在数据库连接的创建和销毁过程中,会涉及到网络连接和资源的开销。通过使用连接池,可以事先创建一定数量的数据库连接,并将其保存在池中。当需要执行数据库操作时,可以从连接池中获取可用的数据库连接来执行操作,操作完毕后将连接放回池中。
Java的数据库连接池技术有多种实现方式,如Tomcat Jdbc Pool (opens new window)、HikariCP (opens new window)、Druid (opens new window)等。
# 3. Java中的对象池实现:Apache Commons Pool2
Apache Commons Pool2是一个开源的Java对象池库,提供了丰富的功能和配置选项,可以方便地实现对象池技术。
Apache Commons Pool2主要包含以下几个核心组件:
ObjectPool:对象池接口,定义了对象池的基本操作,如获取对象、释放对象等。
PooledObjectFactory:对象工厂接口,用于创建和销毁池中的对象。开发人员需要实现该接口来自定义对象的创建和销毁过程。
PooledObject:池化对象接口,表示池中的对象。该接口继承了可关闭接口,可以在对象被归还到池中时执行一些清理操作。
GenericObjectPool:通用对象池实现,是Apache Commons Pool2提供的一个对象池实现。可以通过配置选项来自定义对象池的行为,如最大对象数量、最大空闲对象数量、对象的生存时间等。
# a 引入依赖
首先,你需要在项目中引入Apache Commons Pool2库的依赖。可以通过Maven来添加依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
# b 创建池化对象
创建池化对象很简单,只要实现PooledObjectFactory
工厂接口就行了。
PooledObjectFactory
是一个池化对象工厂接口,定义了生成对象、激活对象、钝化对象、销毁对象的方法,如下:
public interface PooledObjectFactory<T> {
PooledObject<T> makeObject();
void activateObject(PooledObject<T> obj);
void passivateObject(PooledObject<T> obj);
boolean validateObject(PooledObject<T> obj);
void destroyObject(PooledObject<T> obj);
}
如果需要使用Apache Commons Pool2,你就需要提供一个PooledObjectFactory
接口的具体实现。一个比较简单的办法就是继承BasePooledObjectFactory
这个抽象类。继承这个抽象类只需要实现两个方法:create()
和wrap(T obj)
。
实现create()
方法很简单,而实现wrap(T obj)
也有捷径,可以使用DefaultPooledObject
类。代码可以参考如下:
@Override
public PooledObject<Foo> wrap(Foo foo) {
return new DefaultPooledObject<>(foo);
}
举个例子,一个完整的实现如下:
package test.test;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
public class StringBufferFactory extends BasePooledObjectFactory<StringBuffer> {
@Override
public StringBuffer create() throws Exception {
return new StringBuffer();
}
@Override
public PooledObject<StringBuffer> wrap(StringBuffer obj) {
return new DefaultPooledObject<>(obj);
}
}
有时候,单用对池内所有对象一视同仁的对象池,并不能解决问题。
例如,有时需要通过key来获取不同的对象,这样就有可能取出不合用的对象的麻烦。
当然,可以通过为每一组参数相同的同类对象建立一个单独的对象池来解决这个问题。
但是,如果使用普通的ObjectPool
来实施这个计策的话,因为普通的PooledObjectFactory
只能生产出大批设置完全一致的对象,就需要为每一组参数相同的对象编写一个单独的PooledObjectFactory
,工作量相当可观。
这种时候可以使用BaseKeyedPooledObjectFactory
来替代BasePooledObjectFactory
。这个类实现了KeyedPooledObjectFactory
接口,和PooledObjectFactory
接口类似,只是在相关的方法中多了Key参数。
# c 创建对象池
在org.apache.commons.pool2.impl
中预设了三个可以直接使用的对象池:GenericObjectPool
、GenericKeyedObjectPool
和SoftReferenceObjectPool
。
通常使用GenericObjectPool
来创建对象池,如果对象池是Keyed的,那么可以使用GenericKeyedObjectPool
来创建对象池。这两个类都提供了丰富的配置选项。这两个对象池的特点是可以设置对象池中的对象特征,包括LIFO方式、最大空闲数、最小空闲数、是否有效性检查等等。两者的区别如前面所述,后者支持Keyed。
而SoftReferenceObjectPool
对象池利用一个java.util.ArrayList
对象来保存对象池里的对象。不过它并不在对象池里直接保存对象本身,而是保存它们的“软引用”(Soft Reference)。这种对象池的特色是:可以保存任意多个对象,不会有容量已满的情况发生;在对象池已空的时候,调用它的borrowObject
方法会自动返回新创建的实例;可以在初始化同时,在池内预先创建一定量的对象;当内存不足的时候,池中的对象可以被Java虚拟机回收。
举个例子:
new GenericObjectPool<>(new StringBufferFactory());
我们也可以使用GenericObjectPoolConfig
来对上面创建的对象池进行一些参数配置,创建的Config
参数可以使用setConfig
方法传给对象池,也可以在对象池的构造方法中作为参数传入。
举个例子:
GenericObjectPoolConfig conf = new GenericObjectPoolConfig();
conf.setMaxTotal(20);
conf.setMaxIdle(10);
...
GenericObjectPool<StringBuffer> pool = new GenericObjectPool<>(new StringBufferFactory(), conf);
# d 使用对象池
对象池使用起来很方便,简单一点就是使用borrowObject
和returnObject
两个方法。
举个例子:
StringBuffer buf = null;
try {
buf = pool.borrowObject();
// 使用buf
...
} catch(IOException e) {
throw e;
} catch(Exception e) {
throw new RuntimeException("Unable to borrow buffer from pool" + e.toString());
} finally {
try {
if(buf != null) {
pool.returnObject(buf);
}
} catch(Exception e) {
// ignored
}
}
# 对象池的缺点
尽管对象池技术有很多优点,但也存在一些缺点:
内存占用:对象池需要事先创建一定数量的对象并保存在内存中,会占用一定的内存空间。如果对象池中的对象数量过多,可能会导致内存占用过高。
初始化和销毁开销:对象池需要在初始化时创建一定数量的对象,并在销毁时释放所有对象。对象的初始化和销毁过程可能会消耗较高的时间和资源。
线程安全性:对象池在多线程环境下需要保证线程安全性,避免多个线程同时获取和释放对象引发的竞争和冲突。实现线程安全的对象池需要考虑同步机制和并发控制等问题。
对象管理复杂性:对象池需要开发人员自行管理对象的生命周期和状态转换等问题。如果对象的状态转换复杂或者对象之间存在依赖关系,可能会增加对象管理的复杂性。
对象池技术在提高性能和资源利用率方面具有明显的优势,但也需要合理地配置和使用,避免出现内存占用过高、初始化和销毁开销过大等问题。
# 总结
对象池通过对象重用技术解决系统资源消耗大、频繁创建销毁对象的问题,可以显著提高系统性能。但不合理的对象池也会造成内存占用过高的问题。因此在系统设计时,需要根据业务需求和资源情况,合理地应用对象池技术。
对象池技术在Java开发中用处甚广,如果使用得当,可以带来很大性能提升。数据库连接池、线程池等技术都大量使用了对象池的理念。掌握对象池的设计和使用方法,可以帮助开发者写出更加高效的Java程序。
祝你变得更强!