摘要
探讨了在短时间内频繁创建对象可能导致内存抖动和GC频繁的问题,提出对象池技术作为解决方案。对象池是一种预先创建并存储对象的技术,它允许重复利用对象而非每次都进行new
操作,从而减少性能损耗。文中介绍了实现对象获取与释放的Pool<T>
接口及其两个具体实现类:非线程安全的SimplePool
和线程安全的SynchronizedPool
。SimplePool
采用数组存储对象,按需初始化,并提供懒加载机制;而SynchronizedPool
则在此基础上增加了同步锁以确保多线程环境下的安全性。最后总结了对象池的优势和适用场景,强调对于包含大量资源初始化工作的对象以及多线程环境中,使用对象池可以节省资源初始化时间,但也指出了其可能带来的状态恢复开销和线程同步成本。
为什么会有对象池
在日常开发的时候,常常会遇到需要段时间内频繁创建对象的这种情形,会频繁出现 new 操作,这就会导致 内存抖动,从而导致 GC 频繁发生,进而产生性能上的问题,比如 UI卡顿等。这个时候使用 对象池技术就可以一定程度上解决这个问题。
所谓对象池,就是一个存放对象的池子,既然是池子,肯定会有自己的最大容量,即最多可以放多少个对象,还有就是有方法从池子中获取对象,同样也有方法往池子中放入对象。
放入对象 或取出对象的实现
对象的获取和对象的释放通过 Pool<T>
接口来实现,具体接口如下:
1 | public interface Pool<T> { |
简单看一下其实现类,共有两个:SimplePool
和 SynchronizedPool
,前者未采用锁(即多线程不安全的),后者对取对象和放入对象都进行了锁操作,是线程安全的。
SimplePool
对象池的简单实现,实现了Pool<T>
接口,用一个数组承载对象,并采用了懒加载的方式(不是一开始就创建 若干个对象,而是在获取之前需要先向 池子 中添加对象)代码如下:
1 | public static class SimplePool<T> implements Pool<T> { |
说明:
根据
SimplePool
的实现,如果一开始就调用acquire()
方法,返回的肯定是空对象,这说明要使用SimplePool
的话,需要先调用release(@NonNull T instance)
方法将对象先放入到对象池中;SimplePool
中的对象并不是一次性都创建出来的,而是每次外部调用release(@NonNull T instance)
才加入到内存中的;不在对象池中的对象才能调用release放入对象池中,否则会抛出
IllegalStateException
;
SynchronizedPool
这个类继承自 SimplePool
,和它的主要区别就是对 acquire
和 release
操作加锁了,所以如果多线程中使用对象池,最好使用这个类。
1 | /** |
总结
使用对象池有什么好处呢,其实我觉得这个玩意跟线程池是差不多的,只不过线程池 功能更明确,只产生线程对象(因为线程的创建是十分昂贵的)。同理,对于对象池,如果对象中存在较多的资源初始化工作,这个时候对象复用很有必要,毕竟节省了大量的资源初始化时间,但是如果对象有很多 运行时需要改变的状态,就不建议使用对象池了,因为如果对象有很多运行时改变的状态,那么在重新放入对象池或者重新从对象池取出使用时,就会存在状态的恢复开销(已经使用过的对象需要reset到初始态)。
另外,多线程场景中使用对象池也是有代价的,需要进行线程同步操作,这部分的操作可能比 使用对象池带来的收益还大,得不偿失。