这两天在整理一套Android的路由框架,在整理的过程中,发现在路由消息传递过程中,传输载体类会大量的生成,对于这种载体类来说,他们本身是可重复利用的,并不需要大量的创建,大量的废弃,所以,我打算引入对象池,来解决类的重复创建问题。 对象的生命周

这两天在整理一套Android的路由框架,在整理的过程中,发现在路由消息传递过程中,传输载体类会大量的生成,对于这种载体类来说,他们本身是可重复利用的,并不需要大量的创建,大量的废弃,所以,我打算引入对象池,来解决类的重复创建问题。

对象的生命周期

Java对象的生命周期大致包括三个阶段:对象的创建,对象的使用,对象的清除。因此,对象的生命周期长度可用如下的表达式表示:T = T1 + T2 +T3。其中T1表示对象的创建时间,T2表示对象的使用时间,而T3则表示其清除时间。由此,我们可以看出,只有T2是真正有效的时间,而T1、T3则是对象本身的开销。所以,避免和减少T1和T3的时间,能有效的提升程序的性能。

对象池

在EffectJava的第五条建议中,明确给出了“避免创建不必要的对象”这样的建议。对于可重复利用的对象,我们不应该频繁的创建销毁,而是应该反复利用。

重复利用对象,我们就需要建一个对象池,把所有可复用的对象都统一放到池中,同时对外提供obtain()方法来获取对象。通常,我们利用静态对象数组来实现对象池。

对象池的初始化策略分为两种,一种是统一初始化,即在特定时机统一初始化池内所有对象,第一次使用的时候会有一定的开销。另一种是即时初始化,就是在使用中进行初始化,每次都会有一定的开销,直到池内充满。

高并发

在对象池创建好之后,我们就需要考虑如何分配、回收对象池的对象了。由于是路由系统的载体类,所以势必会有多线程进行访问,我们还要考虑到线程安全,以及并发性能。

关于线程安全,一般的做法是通过synchronized关键字来进行线程同步。不过这么做的话,相当于是对obtain()函数加了悲观锁。这么做虽然可以有效的防止同步问题,而且synchronized关键字也经过了JVM多次性能优化,不过,在性能上依然不能满足高并发的要求。

这时候我们参考可以参考ConcurrentHashMap,利用CAS算法(非阻塞同步算法)实现乐观锁。我们在类中创建一个AtomicBoolean变量,命名为isIdle,默认值为true(空闲状态),当多个线程去争抢一个对象的时候,会调用isIdle.compareAndSet(true,false),该方法会返回boolean类型的结果,如果返回true,则表明竞争成功,isIdle被设置为false(占用中),如果返回false,则表明该类已经被别的线程所占用,重新申请另外的对象,直到该对象的isIdle竞争成功。

相关代码如下:

// 顺序计数器

private static AtomicInteger sIndex;

// 空闲标识

AtomicBoolean isIdle;

// 计数器重置参数

private static final int RESET_NUM = 1000;

public static ConcurrentRouterRequest obtain(){

// 获取下一个计数器的值

int index = sIndex.getAndIncrement();

// 如果计数器过大,则置0

if(index>RESET_NUM){

sIndex.compareAndSet(index,0);

if(index>RESET_NUM*2){

sIndex.set(0);

}

}

// 计算计数器对应对象数组的对象下标

int num = index&(length-1);

// 获取对应对象

ConcurrentRouterRequest target = table[num];

// 竞争该对象的空闲标识,其中第一个参数true是我们希望的值,第二个参数是需要设置的值false。

// 如果设置成功,那么直接返回target,如果失败,则继续寻找下一个空闲的对象。

if(target.isIdle.compareAndSet(true,false)){

return target;

}else{

return obtain();

}

}

这里要说一下,关于对象池如何分配对象的问题,这里采用的是顺序分配,每次获取都会在对象池中往后顺延一位。同时,对象池的长度默认为2的N次方,这样做的好处是通过按位与计算,能快速找出对象池中的数组下标,这个模仿了HashMap中的Hash值存储。

测试对比

模拟的条件是100条线程并发请求,每单个线程请求1000次,总计十万次并发。

时间开销

内存开销

新建对象

220ms

1.53MB

利用对象池

180ms

1.25KB

第一种情况是直接用new关键字新建对象,处理请求,最后的结果是耗时220ms,内存消耗100000*16(byte)=1.53MB。

第二种情况是使用对象池处理请求,最后耗时180ms,内存消耗为64*20(byte)=1.25KB。

通过对比我们可以看出,首先,由于减少了对象的创建,虽然多了同步,不过在时间性能上,对象池还是优于新建对象,大概提升了20%左右的性能。该时间还不包括GC回收的开销,如果加上的话,性能提升会更明显。其次,在内存上面,两者的差距是1000倍左右的,极大地节省了内存的开销,减少了GC的触发。

对象池实现

下面放一个纯Java实现的高并发对象池

对象池:

public final class ObjectPool {

private OBJECT[] mTable;

private AtomicInteger mOrderNumber;

public static int RESET_NUM = 1000;

public ObjectPool(OBJECT[] inputArray) {

mOrderNumber = new AtomicInteger(0);

mTable = inputArray;

if (mTable == null) {

throw new NullPointerException("The input array is null.");

}

int length = inputArray.length;

if ((length & length - 1) != 0) {

throw new RuntimeException("The length of input array is not 2^n.");

}

}

public void recycle(OBJECT object) {

object.isIdle.set(true);

}

public OBJECT obtain() {

int index = mOrderNumber.getAndIncrement();

if (index > RESET_NUM) {

mOrderNumber.compareAndSet(index, 0);

if (index > RESET_NUM * 2) {

mOrderNumber.set(0);

}

}

int num = index & (mTable.length - 1);

OBJECT target = mTable[num];

if (target.isIdle.compareAndSet(true, false)) {

return target;

} else {

// 注意:此处可能会因为OBJECT回收不及时,而导致栈溢出。

// 请增加retryTime参数,以及retryTime过多后的判断。

// 具体思路请参考

// https://github.com/SpinyTech/ModularizationArchitecture/blob/master/macore/src/main/java/com/spinytech/macore/router/RouterRequest.java

// 中的obtain()及obtain(int retryTime);方法

return obtain();

}

}

public abstract static class RecyclableObject {

AtomicBoolean isIdle = new AtomicBoolean(true);

}

}

测试类:

public class Test {

public static void main(String[] args) {

TestObject[] array = new TestObject[32];

for(int i = 0 ; i < 32 ;i++){

array[i] = new TestObject();

}

final ObjectPool objectPool = new ObjectPool<>(array);

for(int i = 0 ; i < 50; i++){

new Thread("Thread:"+i){

@Override

public void run() {

super.run();

for(int j = 0 ; j < 50 ; j++){

TestObject testObject = objectPool.obtain();

testObject.print(getName(),"--index:"+j);

objectPool.recycle(testObject);

}

}

}.start();

}

}

static class TestObject extends ObjectPool.RecyclableObject{

public TestObject(){

}

public void print(String thread, String index){

System.out.println(thread+index);

}

}

}

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐