这次要介绍1下对象池模式(Object Pool Pattern),这个模式为常见 23 种设计模式以外的设计模式,介绍的初衷主要是在平时的 android 开发中常常会看到,比如 ThreadPool 和 MessagePool 等。
在 java 中,所有对象的内存由虚拟机管理,所以在某些情况下,需要频繁创建1些生命周期很短使用完以后就能够立即烧毁,但是数量很大的对象集合,那末此时 GC 的次数必定会增加,这时候候为了减小系统 GC 的压力,对象池模式就很适用了。对象池模式也是创建型模式之1,它不是根据使用动态的分配和烧毁内存,而是保护1个已初始化好若干对象的对象池以供使用。客户端使用的时候从对象池中去申请1个对象,当该对象使用完以后,客户端会返回给对象池,而不是立即烧毁它,这步操作可以手动或自动完成。
从 Java 语言的特性来分析1下,在 Java 中,对象的生命周期大致包括3个阶段:对象的创建,对象的使用,对象的清除。因此,对象的生命周期长度可用以下的表达式表示:T = T1 + T2 +T3。其中T1表示对象的创建时间,T2 表示对象的使用时间,而 T3 则表示其清除时间。由此,我们可以看出,只有 T2 是真正有效的时间,而 T1、T3 则是对象本身的开消。下面再看看 T1、T3 在对象的全部生命周期中所占的比例。Java对象是通过构造函数来创建的,在这1进程中,该构造函数链中的所有构造函数也都会被自动调用。另外,默许情况下,调用类的构造函数时,Java 会把变量初始化成肯定的值:所有的对象被设置成 null,整数变量(byte、short、int、long)设置成 0, float 和 double 变量设置成 0.0,逻辑值设置成false。所以用new关键字来新建1个对象的时间开消是很大的,以下表所示:
运算操作 | 示例 | 标准化时间 |
---|---|---|
本地赋值 | i = n | 1.0 |
实例赋值 | this.i = n | 1.2 |
方法调用 | Funct() | 5.9 |
新建对象 | New Object() | 980 |
新建数组 | New int[10] | 3100 |
从表中可以看出,新建1个对象需要980个单位的时间,是本地赋值时间的980倍,是方法调用时间的166倍,而若新建1个数组所花费的时间就更多了。
再看清除对象的进程,我们知道,Java 语言的1个优势,就是 Java 程序员勿需再像 C/C++ 程序员那样,显式地释放对象,而由称为垃圾搜集器(Garbage Collector)的自动内存管理系统,定时或在内存凸现出不足时,自动回收垃圾对象所占的内存。凡事有益总也有弊,这虽然为 Java 程序设计者提供了极大的方便,但同时它也带来了较大的性能开消。这类开消包括两方面,首先是对象管理开消,GC为了能够正确释放对象,它必须监控每个对象的运行状态,包括对象的申请、援用、被援用、赋值等。其次,在 GC 开始回收“垃圾”对象时,系统会暂停利用程序的履行,而独自占用 CPU。
因此,如果要改良利用程序的性能,1方面应尽可能减少创建新对象的次数;同时,还应尽可能减少 T1、T3 的时间,而这些都可以通过对象池技术来实现。所以对象池主要是用来提升性能,在某些情况下,对象池对性能有极大的帮助。但是还有1点需要注意,对象池会增加对象生命周期的复杂度,这是由于从对象池获得的对象和返还给对象池的对象都没有真实的创建或烧毁。
PS:对技术感兴趣的同鞋加群5446459721起交换。
java/android 设计模式学习笔记目录
从上面的介绍可以总结:对象池模式适用于“需要使用到大量的同1类对象,这些对象的初始化会消耗大量的系统资源,而且它们只需要使用很短的时间,这类操作会对系统的性能有1定影响”的情况,总结1下就是在下面两种分配模式下可以选择使用对象池:
1般对象池模式的 uml 类图如图所示,他有3个角色
我们先以 android 源码中的 MessagePool 为例子来分析1下 ObjectPool 在实际开发中的使用效果,以后写1个小的 demo 。
在 android 中使用 new Message() , Message.obtain() 和 Handler.obtainMessage() 都能够取得1个 Message 对象,但是为何后两个的效力会高于前者呢?先来看看源码的 Message.obtain() 函数:
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
返回的是 sPool 这个对象,继续看看 Handler.obtainMessage() 函数:
/**
* Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
* creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
* If you don't want that facility, just call Message.obtain() instead.
*/
public final Message obtainMessage()
{
return Message.obtain(this);
}
1样是调用到了 Message.obtain() 函数,那末我们就从这个函数开始分析, Message 类是如何构建 MessagePool 这个角色的呢?看看这几处的代码:
//变量的构建
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
private static boolean gCheckRecycle = true;
Message 对象的回收
/**
* Return a Message instance to the global pool.
* <p>
* You MUST NOT touch the Message after calling this function because it has
* effectively been freed. It is an error to recycle a message that is currently
* enqueued or that is in the process of being delivered to a Handler.
* </p>
*/
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
从这几处代码可以很清楚的看到:
我们参照上面的 uml 类图写1个 demo,首先要定义 Reusable 这个角色,为了直观,我们将该类定义了很多成员变量,用来摹拟 expensive initialization:
Reusable.class
public class Reusable {
public String a;
public String b;
public String c;
public String d;
public String e;
public String f;
public String g;
public ArrayList<String> h;
public ArrayList<String> i;
public ArrayList<String> j;
public ArrayList<String> k;
public ArrayList<String> l;
public Reusable(){
h = new ArrayList<>();
i = new ArrayList<>();
j = new ArrayList<>();
k = new ArrayList<>();
l = new ArrayList<>();
}
}
然后用1个 IReusablePool.class接口来定义对象池的基本行动:
public interface IReusablePool {
Reusable requireReusable();
void releaseReusable(Reusable reusable);
void setMaxPoolSize(int size);
}
最后实现该接口:
public class ReusablePool implements IReusablePool {
private static final String TAG = "ReusablePool";
private static volatile ReusablePool instance = null;
private static List<Reusable> available = new ArrayList<>();
private static List<Reusable> inUse = new ArrayList<>();
private static final byte[] lock = new byte[]{};
private static int maxSize = 5;
private int currentSize = 0;
private ReusablePool() {
available = new ArrayList<>();
inUse = new ArrayList<>();
}
public static ReusablePool getInstance() {
if (instance == null) {
synchronized (ReusablePool.class) {
if (instance == null) {
instance = new ReusablePool();
}
}
}
return instance;
}
@Override
public Reusable requireReusable() {
synchronized (lock) {
if (currentSize >= maxSize) {
throw new RuntimeException("pool has gotten its maximum size");
}
if (available.size() > 0) {
Reusable reusable = available.get(0);
available.remove(0);
currentSize++;
inUse.add(reusable);
return reusable;
} else {
Reusable reusable = new Reusable();
inUse.add(reusable);
currentSize++;
return reusable;
}
}
}
@Override
public void releaseReusable(Reusable reusable) {
if (reusable != null) {
reusable.a = null;
reusable.b = null;
reusable.c = null;
reusable.d = null;
reusable.e = null;
reusable.f = null;
reusable.g = null;
reusable.h.clear();
reusable.i.clear();
reusable.j.clear();
reusable.k.clear();
reusable.l.clear();
}
synchronized (lock) {
inUse.remove(reusable);
available.add(reusable);
currentSize--;
}
}
@Override
public void setMaxPoolSize(int size) {
synchronized (lock) {
maxSize = size;
}
}
}
最后测试程序:
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_get:
new Thread(new Runnable() {
@Override
public void run() {
try {
Reusable reusable = ReusablePool.getInstance().requireReusable();
Log.e("CORRECT", "get a Reusable object " + reusable);
Thread.sleep(5000);
ReusablePool.getInstance().releaseReusable(reusable);
}catch (Exception e){
Log.e("ERROR", e.getMessage());
}
}
}).start();
break;
}
}
需要说明的是:
对象池模式从上面来分析可以说是用途很大,但是这个模式1定要注意使用的场景,也就是最上面提到的两点:“对象以固定的速度不断地分配,垃圾搜集时间逐渐增加,内存使用率随之增大”和“对象分配存在爆发期,而每次爆发都会致使系统迟滞,并伴随明显的GC中断”,其他的1般情况下最好不要使用对象池模式,我们后面还会提到它的缺点和很多人对其的批评。
优点上面就已论述的很清楚了,对那些”the rate of instantiation and destruction of a class is high” 和 “the cost of initializing a class instance is high”的场景优化是及其明显的,例如 database connections,socket connections,threads 和类似于 fonts 和 Bitmap 之类的对象。
上面已提过了,使用对象池模式时,每次进行回收操作前都需要将该对象的相干成员变量重置,如果不重置将会致使下1次重复使用该对象时候出现预感不到的毛病,同时的,如果回收对象的成员变量很大,不重置还可能会出现内存 OOM 和信息泄漏等问题。另外的,对象池模式绝大多数情况下都是在多线程中访问的,所以做好同步工作也是极为重要的。原文:https://en.wikipedia.org/wiki/Object_pool_pattern#Pitfalls
除以上两点以外,还需要注意以下问题:
1些开发者不建议在某些语言例如 Java 中使用对象池模式,特别是对象只使用内存并且不会持有其他资源的语言。这些反对者持有的观点是 new 的操作只需要 10 条指令,而使用对象池模式则需要成百上千条指令,明显增加了复杂度,而且 GC 操作只会去扫描“活着”的对象援用所指向的内存,而不是它们的成员变量所使用的那块内存,这就意味着,任何没有援用的“死亡”对象都能够被 GC 以很小的代价跳过,相反如果使用对象池模式,持有大量“活着“的对象反而会增加 GC 的时间。原文:https://en.wikipedia.org/wiki/Object_pool_pattern#Criticism
https://github.com/zhaozepeng/Design-Patterns/tree/master/ObjectPoolPattern
http://www.cnblogs.com/xinye/p/3907642.html
https://en.wikipedia.org/wiki/Object_pool_pattern
http://blog.csdn.net/liyangbing315/article/details/4942870
http://www.infoq.com/cn/news/2015/07/ClojureWerkz
http://blog.csdn.net/xplee0576/article/details/46875555