关于通用的对象池

前言

首先,需要承认本文可能有些标题党。因为这里的通用只是在一个特定情景下,为了使用对象池的技术可以避免编写重复的模板代码。
在开始编写代码前,先交代一下该需求出现的背景:整个应用的架构采用了 MVVM 的模式,在 ViewModel 中抽象出一个视图对象,该对象通过业务逻辑中的一个可变对象转换而成。至于为什么不直接使用该可变对象,有一下几个原因:

  1. 需要该对象是不可变的,以良好的支持在哈希结构的容器中使用。
  2. 有些属性需要通过计算获得,也就是说该对象是业务逻辑中的可变对象的扩展。
  3. 以及在可变对象发生变化时保持不变,这样可以有效的找出每次变化后,视图会发生变化的节点。

但是一旦变化速度过快或者可变对象数量过多(例如一个列表),就会导致每次视图刷新时创建过多的小对象,这会导致严重的性能瓶颈,这时利用对象池来复用对象避免小对象的创建和 GC 开销就显得非常有必要了。而我们的目标就很简单:可以把 A 对象转换成 B 对象,并且 B 对象是一个可复用的、在生命周期内的不可变对象,使得转换的开销尽可能的低。

简单的对象池

例如需要把 String 转换成 User,在没有考虑复用时,User 代码是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
public class User {

private final String name;

public User(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

为了能让它变成一个可复用的对象,需要把它变成一个在生命周期中不可变对象(就是在它从对象池中获取后到被回收前它内部的值不会发生变化)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class User {

private String name;

public User() {
}

void init(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

这样,每次从对象池中取出对象时就通过调用 init 函数代替构造器,并且外部是没有办法随意改变对象内部的值的,内部值的改变时间点完全可控。接下来为该对象编写对象池。

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
public class User {

private final ObjectPool pool;
private String name;

private User(ObjectPool pool) {
this.pool = pool;
}

void init(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void recycle() {
int curCount = pool.cache.size();

if (curCount == pool.cacheCount) {
return;
}

pool.cache.push(this);
}

public static class ObjectPool {

private final int cacheCount;
private final Queue<User> cache;

public ObjectPool(int cacheCount) {
this.cacheCount = cacheCount;
this.cache = new ArrayDeque<>(cacheCount);
}

public User get(String name) {
User user = cache.peek();
if (user == null) {
user = new User(this);
}
user.init(name);
return user;
}
}
}

至此,User 类型的对象池就编写完成,从对象池中可以利用 String 对象获得一个 User 对象,并且该 User 对象是可复用的。

对象池模板

一般情况下,视图类型不止一种,于是如果按照上面那样,每个类型都为其编写额外的对象池相关代码太过于繁琐。这时就应该利用泛型机制,编写一个通用的对象池模板类型,该模板支持利用 A 类型实例通过一些变换转得到 B 类型实例,并且 B 类型可以通过该对象池完成复用。于是对象池模板就应该是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static class ObjectPool<A,B> {

private final int cacheCount;
private final Queue<User> cache;

public ObjectPool(int cacheCount) {
this.cacheCount = cacheCount;
this.cache = new ArrayDeque<>(cacheCount);
}

public B get(A name) {
B o = cache.peek();
if (o == null) {
o = create();
}
o.init(name);
return o;
}

protect abstract B create();
}

一个可回收的对象应该是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static abstract class Poolable<A,B> {

private final ObjectPool<A, B> pool;

protected Poolable(ObjectPool<A, B> pool) {
this.pool = pool;
}

protected abstract void init(A a);

public final void recycle() {
onRecycle();
pool.recycle((P) this);
}

protected void onRecycle() {

}
}

在这里就会发现一个问题,实际上 B 需要是 Poolable 的子类型。要在泛型中得到子类型的类型,可以学习 Enum 类型的做法。

1
java.lang.Enum<E extends java.lang.Enum<E>>

上面是 Enum 的类型描述,Enum 的泛型类型指定了该泛型类型需要继承至自己本身,以此可以在父类中导出子类的类型。那么,我们完整的对象池模板代码如下:

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
public abstract class ObjectPool<V, P extends Poolable<V, P>> {

private final Queue<P> cache;
private final int cacheCount;

protected ObjectPool(int cacheCount) {
this.cacheCount = cacheCount;
this.cache = new ArrayDeque<>(cacheCount);
}

public P get(V v) {
P o = cache.poll();
if (o == null) {
o = create();
}

o.init(v);
return o;
}

protected abstract P create();

private void recycle(P o) {
if (cache.size() == cacheCount) {
return;
}
cache.add(o);
}

public static abstract class Poolable<V, P extends Poolable<V, P>> {

private final ObjectPool<V, P> pool;

protected Poolable(ObjectPool<V, P> pool) {
this.pool = pool;
}

protected abstract void init(V v);

public final void recycle() {
onRecycle();
pool.recycle((P) this);
}

protected void onRecycle() {

}
}
}

通过上面这个对象池模板,对于 User 类型想要拥有对象复用功能只需要继承至 Poolable 并实现一些简单的方法即可。

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
public class User extends Poolable<String, User> {

private static final int CACHE_COUNT = 10;

public static final ObjectPool<String, User> POOL = new ObjectPool<String, User>(CACHE_COUNT) {
@Override
protected User create() {
return new User(this);
}
};

private String name;

private User(ObjectPool<String, User> pool) {
super(pool);
}

@Override
protected void init(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

可以看出相比于最开始为 User 实现对象池复用功能要省时省力了很多。