1、简介
我们在服务器开发的过程中,往往会有一些对象,它的创建和初始化需要的时间比较长,比如数据库连接,网络IO,大数据对象等。在大量使用这些对象时,如果不采用一些技术优化,就会造成一些不可忽略的性能影响。一种办法就是使用对象池,每次创建的对象并不实际销毁,而是缓存在对象池中,下次使用的时候,不用再重新创建,直接从对象池的缓存中取即可。
为了避免重新造轮子,我们可以使用优秀的开源对象池化组件apache-commons-pool2,它对对象池化操作进行了很好的封装,我们只需要根据自己的业务需求重写或实现部分接口即可,使用它可以快速的创建一个方便,简单,强大对象连接池管理类。
基本概念
pooled object : 被池化的对象,也就是我们打算放进池中的对象,如数据库连接中的Connection对象。
pooled object factory :被池化对象的工厂,我们在这个工厂中创建要放入池中的对象。
object pool : 存放对象的池子,该工具包的核心。
object pool config : 池子的配置,如设置池中最多可以方多少对象,可以最多空闲多少对象等。
2、引入依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.3</version>
</dependency>
3、普通池子的使用
定义 打算放入池中的对象
public class Student {
private String name;
private int age;
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
定义 对象工厂
public class StudentPoolableObjectFactory extends BasePooledObjectFactory<Student> {
@Override
public synchronized Student create() throws Exception {
return new Student("小明",12);
}
@Override
public PooledObject<Student> wrap(Student student) {
return new DefaultPooledObject<>(student);
}
}
BasePooledObjectFactory是一个抽象工厂类,当池子中没有空闲的对象 需要创建新对象是会调用这里的方法,然后将生成的对象放入池中。由于池子中的对象必须是PooledObject类型的 所以,我们在create中创建对象,然后在wrap中将之分装成DefaultPooledObject对象。
创建池子并使用
public static void main(String[] args) {
// 创建池化对象工厂
PooledObjectFactory<Student> factory = new StudentPoolableObjectFactory();
// 创建池子的配置
GenericObjectPoolConfig conf = new GenericObjectPoolConfig();
conf.setMinIdle(5);
conf.setMinIdle(3);
conf.setMaxTotal(10);
// 创建池子
GenericObjectPool<Student> pool = new GenericObjectPool<Student>(factory,conf);
for (int i = 0; i < 20; i++) {
new Thread(()->{
try {
// 从池子里获取对象
Student student = pool.borrowObject();
// 使用对象
System.out.println(Thread.currentThread().getName()+" 正在使用: "+student);
Thread.sleep(2000);
// 归还对象
pool.returnObject(student);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
创建一个池子需要工厂对象和配置对象,配置对象也可不传,会有默认配置。使用borrowObject()函数从池子中取出对象,当容器中没有可用对象且池中对象数量已达到池子的最大容量时,borrowObject()就会阻塞等待对象被归还。所以用完记得使用returnObject(xxx)归还对象。
4、带 Key 池子的使用
从上面关于普通池子的使用中我们发现,池子中虽然可以存放多个对象,但是这些对象是相同的,因为创建他们的工厂方法是同一个。对于某些场景这可能就不太合适了,例如在数据库连接池中我们想要池中的对象可以连接到不同的数据库。
这时候我们就需要带 key 的池子,这种池子会将池子划分为不同的区域,用key标识不同的池子。
这样我们就可以在工厂方法中根据不同的 key 生成不同的对象,然后在从池子中借取对象时,也要指定key,从key特定的区域内获取。
对象工厂
public class KeyedStudentPooledObjectFactory extends BaseKeyedPooledObjectFactory<String,Student> {
@Override
public Student create(String key) throws Exception {
if("database01".equals(key)){
// 创建第一个数据库的连接
return new Student("小红",12);
}else if ("database02".equals(key)){
return new Student("小明",15);
}else {
return null;
}
}
@Override
public PooledObject<Student> wrap(Student value) {
return new DefaultPooledObject<>(value);
}
}
与普通池子相比,BaseKeyedPooledObjectFactory工厂的创建方法会传入一个key,我们可以根据可以生成不同的对象。
构建池子并使用
public static void main(String[] args) {
KeyedPooledObjectFactory<String, Student> factory = new KeyedStudentPooledObjectFactory();
GenericKeyedObjectPoolConfig conf = new GenericKeyedObjectPoolConfig();
conf.setMaxTotalPerKey(2);
GenericKeyedObjectPool<String, Student> pool = new GenericKeyedObjectPool<>(factory,conf);
for (int i = 0; i < 20; i++) {
int finalI = i;
new Thread(() -> {
try {
// 从池中借一个对象
Student sss = pool.borrowObject("database0" + (finalI % 2 + 1));
// 使用对象
System.out.println(sss);
// 归还对象
pool.returnObject("database0"+ (finalI % 2 + 1),sss);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
GenericKeyedObjectPoolConfig是带key池子的配置文件,相关配置都多了一个xxxPerKey 的配置,如setMaxTotalPerKey设置每个区域的最大容量。