Java中的对象池模式

Java对象的生命周期分析:

Java对象的生命周期大致包括三个阶段:

对象的创建,对象的使用, 对象的清除。 因此,对象的生命周期长度可用如下的表达式表示: T = T1 + T2 + T3.其中T1表示对象的创建时间,T2表示对象的使用时间,而T3则表示对象的清除时间。由此,我们可以看出,只有T2是真正有效的时间,而T1,T3则是对象本身的开销。下面再看看T1, T3在对象的整个生命周期中所占的比例。

我们知道,Java对象是通过构造函数来创建的,在这一过程中,该构造函数链中的所有构造函数也都会被自动调用。另外,默认情况下,调用类的构造函数时,Java会把变量初始化成确定的值:所有的对像被设置成null,整数变量(byte, short, int , long)设置成0,float和double变量设置成0.0,逻辑值设置成false,所以用new关键字来新建一个对象的时间开销是很大的,如表1所示。

表1 一些操作所消耗的时间的对照表

运算操作

示例

标准化时间

本地赋值

i = n

1.0

实例赋值

this.i = n

1.2

方法调用

Funct()

5.9

新建对象

New Object()

980

新建数组

New int[10]

3100

从表一可以看出,新建一个对象需要的980个单位的时间,是本地赋值时间的980倍,是方法调用时间的166倍,而若新建一个数组所花费的时间就更多了。

再看清除对象的过程。我们知道,Java语言的一个优势,就是Java程序员不需要像C/C++程序员那样,显式地释放对象,而由称为垃圾收集器(Garbage Colletor)的自动内存管理系统,定时或内存凸显不足时,自动回收垃圾对象所占的内存。凡事有利总也有弊,这虽然为Java程序员设计者提供了极大的方便,但同时它也带来了较大的性能开销。这种开销包括两方面:首先是对象管理的开销,GC为了能够正确释放对象,它必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等。其次,在GC开始回收”垃圾”对象时,系统会暂停应用程序的执行,而独占CPU.因此,如果要改善应用程序的性能,一方面应尽量减少创建对象的次数;同时,还应尽量减少T1,T3的时间。而这些均可以通过对象池技术来实现。

对象池技术的基本原理: 对象池技术基本原理的核心有两点:缓存和共享,即对于那些被频繁使用的对象,在使用完后,不立即将它们释放,而是将它们缓存起来,以供后续的应用程序重复使用,从而减少创建对象和释放对象的次数,进而改善应用程序的性能。事实上,由于对象池技术将对象限制在一定的数量,也有效地减少了应用程序在内存上的开销。

实现一个对象池,一般会涉及到如下的类:

⑴对象池工厂(ObjectPoolFactory)类

该类主要用于管理相同类型和设置的对象池(ObjectPool),它一般包含如下两个方法:createPool:用于创建特定类型和设置的对象池;destroyPool:用于释放指定的对象池;

同时为了保证ObjectPoolFactory的单一实例,可以采用Singleton设计模式,见下述getInstance方法的实现:

public synchronized ObjectPoolFactory getInstance() {

if (poolFactory == null) {

poolFactory = new ObjectPoolFactory();

return poolFactory;

}

}

⑵参数对象(ParameterObject)类

该类主要用于封装所创建对象池的一些属性参数,如池中可存放对象的数目的最大值(maxCount)、最小值(minCount)等。

对象池(ObjectPool)类

用于管理要被池化对象的借出和归还,并通知PoolableObjectFactory完成相应的工作。它一般包含如下两个方法:

◆   getObject: 用于从池中借出对象;

◆   returnObject:将池化对象返回到池中,并通知所有处于等待状态的线程;

◆   池化对象工厂(PoolableObjectFactory)类

该类主要负责管理池化对象的生命周期,就简单来说,一般包含对象的创建的创建和销毁。该类同ObjectPoolFactory一样,也可将其实现为单实例。

通用对象池的实现:

对象池的构造和治理可以按照多种方式实现。最灵活的方式是将池化对象的Class类型在对象池之外指定,即在ObjectPoolFactory类创建对象池时,动态指定该对象池所池化对象的Class类型,其实现代码如下:

public ObjectPool createPool(ParameterObject paraObj, Class clsType) {

return new ObjectPool(paraObj, clsType);

}

其中,paraObj参数用于指定对象池对的特征属性,clsType参数则指定了该对象池所存放对象的类型。对象池(ObjectPool)创建以后,下面就是利用它来治理对象了,具体实现如下;

public class ObjectPool {

private ParameteObject paraObj; //该对象池的属性参数对象

private Class clsType; //该对象池中所存放对象得到类型

private int currentNum = 0; //该对象池当前已创建的对象数目

private Object currentObj; //该对象池当前可以

}

package com.test;

public class ObjectPoolFactory {

/**

* 饿汉式单例类

*/

private static final ObjectPoolFactory factory = new ObjectPoolFactory();

/**

* 私有默认的构造子

*/

private ObjectPoolFactory() {

}

/**

* 静态工厂方法

@return  池工厂

*/

public static ObjectPoolFactory
getInstance() {

return factory;

}

/**

*

@param paraObj  对象池参数对象

@param clsType   所创建对象类型

@return   对象池

*/

public ObjectPool createPool(ParameterObject paraObj, Class clsType) {

return new ObjectPool(paraObj,
clsType);

}

}

package com.test;

import java.util.Vector;

public class ObjectPool {

private ParameterObject paraObj; // 该对象池的属性参数对象

private Class clsType; // 该对象池中所存放对象的类型

private int currentNum =
0; // 该对象池当前已创建的对象数目

private Object currentObj; // 该对象池当前可以借出的对象

private Vector pool; // 用于存放对象的池

public ObjectPool(ParameterObject paraObj, Class clsType) {

this.paraObj = paraObj;

this.clsType = clsType;

pool = new Vector();

}

public Object getObject() {

if (pool.size()
<= paraObj.getMinCount()) {

if (currentNum <= paraObj.getMaxCount())
{ // 如果当前池中无对象可用,

// 而且已创建的对象数目小于所限制的最大值,

// 就利用PoolObjectFactory创建一个新的对象

PoolableObjectFactory objFactory = PoolableObjectFactory

.getInstance();

currentObj = objFactory.createObject(clsType);

currentNum++;

else {

// 如果当前池中无对象可用,而且所创建的对象数目已达到所限制的最大值,

// 就只能等待其它线程返回对象到池中

synchronized (this)
{

try {

wait();

catch (InterruptedException e) {

System.out.println(e.getMessage());

e.printStackTrace();

}

currentObj = pool.firstElement();

}

}

else {

// 如果当前池中有可用的对象,就直接从池中取出对象

currentObj = pool.firstElement();

}

return currentObj;

}

public void returnObject(Object
obj) { // 确保对象具有正确的类型

if (clsType.isInstance(obj))
{

pool.addElement(obj);

synchronized (this)
{

notifyAll();

}

else {

}

}

}

package com.test;

public class PoolableObjectFactory {

private static PoolableObjectFactory factory;

private PoolableObjectFactory() {

}

public static synchronized PoolableObjectFactory
getInstance() {

if (factory == null)
{

factory = new PoolableObjectFactory();

}

return factory;

}

public Object createObject(Class clsType) {

Object obj = null;

try {

obj = clsType.newInstance();

catch (InstantiationException e) {

e.printStackTrace();

catch (IllegalAccessException e) {

e.printStackTrace();

}

return obj;

}

}

package com.test;

public class ParameterObject {

private int MinCount;

private int MaxCount;

public ParameterObject(int MaxCount, int MinCount)
{

this.MaxCount =
MaxCount;

this.MinCount =
MinCount;

}

public int getMinCount()
{

return MinCount;

}

public void setMinCount(int minCount)
{

MinCount = minCount;

}

public int getMaxCount()
{

return MaxCount;

}

public void setMaxCount(int maxCount)
{

MaxCount = maxCount;

}

}

package com.test;

public class TestPool {

public static void main(String[]
args) {

ObjectPoolFactory poolFactory = ObjectPoolFactory.getInstance();

ParameterObject paraObj = new ParameterObject(2,1);

ObjectPool pool = poolFactory.createPool(paraObj, StringBuffer.class);

StringBuffer buffer = (StringBuffer)pool.getObject();

buffer.append("hello");

System.out.println(buffer.toString());

}

}

String中的对象池模式:我们知道得到String对象有两种办法:String str1=”hello”;String str2=new String(“hello”);那么这两种创建String对象的方法有什么差异吗?当然有差异,差异在于第一种方法在对象池中拿对象,第二种方法直接生成新的对象。在JDK5.0里面,Java虚拟机在启动的时候会实例化9个对象池,这9个对象池分别用来存储8种基本类型的包装类对象和String对象。当我们在程序中直接用双引号括起来一个字符串是,JVM就到String的对象池里面去找看是否有一个值相同的对象,如果有,就拿现成的对象,如果没有就在对象池里创建一个对象,并返回。所以我们发现下面的代码输出true:
String str1=”hello” ;String str2=”hello”;

System.out.println(str1==str2);这说明str1和str2指向同一个对象,因为它们都是在对象池中拿到的,而下面的代码输出为false;因为在任何情况下,只要你去new一个String对象那都是创建了新的对象。与此类似的,在JDK5.0里面8中基本类型的包装类也有这样的差异:Interger i1=5; 在对象池中拿到Integer i2=5;所以i1==i2 Integer i3= new Integer(5); //重新创建对象,所以i2
!= i3对象池的存在是为了避免频繁的创建和销毁对象的而影响系统性能,那我们自己写的类是否也可以使用对象池呢?当然可以,考察下面的代码:

package com.test;

import java.util.HashSet;

import java.util.Set;

class Student {

private int id;

private String name;

private static Set<Student> students = new HashSet<Student>();

public Student(int id,
String name) {

this.id = id;

this.name = name;

}

public static Student
getInstance(int id, String name) {

for (Student st : students)
{

if (id == st.id  &&
name.equals(st.name)) {

return st;

}

}

Student temp = new Student(id, name);

students.add(temp);

return temp;

}

}

public class Test4 {

public static void main(String[]
args) {

Student st1 = Student.getInstance(0, "liyangbing");

Student st2 = Student.getInstance(0, "liyangbing");

System.out.println("s1 == s2 is " +
(st1 == st2)); //对象st2是从对象池中取得,故str1 == str2

Student st3 = new Student(0, "liyangbing");

Student st4 = new Student(0, "liyangbing");

System.out.println("st3 == st4 is " +
(st3 == st4)); //对象st3和st4都是创建的,故st3
!= st4

}

Java曾经号称是纯面向对象的语言,那么我们经常用的8中基本数据类型算不算对象呢?答案很肯定不算,那么java又怎能称得上是纯面向对像的语言呢?为此,SUN公司特意的推出了8中基本数据类型的包装类,这样java语言才算是称得上纯面向对象的语言,那么在JDK中就有9个对象池,8个基本类包装类对象和String类对象。请注意:在J2ME里面没有对象池的概念。

结束语:

恰当地使用对象池技术,能有效地改善应用程序的性能。目前,对象池技术已得到广泛的应用,如对于网络和数据库连接这类重量级的对象,一般都会采用对象池技术。但在使用对象池技术时也要注意如下问题:

并非任何情况下都适合采用对象池技术,基本上,只在重复生成某种对象的操作成为影响性能的关键因素的时候,才适合采用对象池技术。而如果进行池化所带来的性能提高并不重要的话,还是不采用对象池技术为佳,以保持代码的简明。

要根据具体情况正确选择对象池的实现方式。如果是创建一个公用的对象池技术实现包,或需要在程序中动态指定所池化对象的Class类型时,才选择通用对象池,而大部分情况下,采用专用对象池就可以了。

public class ObjectPool {
 private ParameterObject paraObj;//该对象池的属性参数对象
 private int currentNum = 0; //该对象池当前已创建的对象数目
 private StringBuffer currentObj;//该对象池当前可以借出的对象
 private Vector pool;//用于存放对象的池
 public ObjectPool(ParameterObject paraObj) {
  this.paraObj = paraObj;
  pool = new Vector();
 }
 public StringBuffer getObject() {
  if (pool.size() <= paraObj.getMinCount()) {
   if (currentNum <= paraObj.getMaxCount()) {
    currentObj = new StringBuffer();
    currentNum++;
   } 
   . . . 
  }
  return currentObj;
 }
 public void returnObject(Object obj) {
  // 确保对象具有正确的类型
  if (StringBuffer.isInstance(obj)) {
   . . . 
  }
 }

Java中的对象池模式的更多相关文章

  1. Java中的对象池技术

    java中的对象池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复重复创建相等变量时节省了很多时间.对象池其实也就是一个内存 ...

  2. Java 中的对象池实现

    点赞再看,动力无限.Hello world : ) 微信搜「程序猿阿朗 」. 本文 Github.com/niumoo/JavaNotes 和 未读代码博客 已经收录,有很多知识点和系列文章. 最近在 ...

  3. java设计模式之实现对象池模式示例分享

    http://www.jb51.net/article/46941.htm 对象池模式经常用在频繁创建.销毁对象,且对象创建.销毁开销很大的场景,比如数据库连接池.线程池.任务队列池等.本代码简单,没 ...

  4. 【Java_基础】java中的常量池

    1.java常量池的介绍 java中的常量池,通常指的是运行时常量池,它是方法区的一部分,一个jvm实例只有一个运行常量池,各线程间共享该运行常量池. java常量池简介:java常量池中保存了一份在 ...

  5. JAVA中JavaBean对象之间属性拷贝的方法

    JAVA中JavaBean对象之间的拷贝通常是用get/set方法,但如果你有两个属性相同的JavaBean或有大部分属性相同的JavaBean,对于这种情况,可以采用以下几个简便方法处理. 下面对这 ...

  6. 【万字图文-原创】 | 学会Java中的线程池,这一篇也许就够了!

    碎碎念 关于JDK源码相关的文章这已经是第四篇了,原创不易,粉丝从几十人到昨天的666人,真的很感谢之前帮我转发文章的一些朋友们. 从16年开始写技术文章,到现在博客园已经发表了222篇文章,大多数都 ...

  7. 《Java并发编程的艺术》 第9章 Java中的线程池

    第9章 Java中的线程池 在开发过程中,合理地使用线程池能带来3个好处: 降低资源消耗.通过重复利用已创建的线程 降低线程创建和销毁造成的消耗. 提高响应速度.当任务到达时,任务可以不需要等到线程创 ...

  8. JAVA中创建线程池的五种方法及比较

    之前写过JAVA中创建线程的三种方法及比较.这次来说说线程池. JAVA中创建线程池主要有两类方法,一类是通过Executors工厂类提供的方法,该类提供了4种不同的线程池可供使用.另一类是通过Thr ...

  9. GoLang设计模式06 - 对象池模式

    这次介绍最后一个创建型模式--对象池模式.顾名思义,对象池模式就是预先初始化创建好多个对象,并将之保存在一个池子里.当需要的时候,客户端就可以从池子里申请一个对象使用,使用完以后再将之放回到池子里.池 ...

  10. 对象池模式(Object Pool Pattern)

    本文节选自<设计模式就该这样学> 1 对象池模式的定义 对象池模式(Object Pool Pattern),是创建型设计模式的一种,将对象预先创建并初始化后放入对象池中,对象提供者就能利 ...

随机推荐

  1. Pandas从入门到放弃

    公众号本文地址:https://mp.weixin.qq.com/s/mSkA5KvL1390Js8_1ZBiyw Pandas简介 Pandas是Panel data(面板数据)和Data anal ...

  2. 基于GitLab+Jenkin-CICD方案实践

    前言 笔录于2022- 官网:https://about.gitlab.com/ 参考文档:https://docs.gitlab.com/ee/ci/ 清华源:清华大学开源软件镜像站 | Tsing ...

  3. PYRAFORMER: 用于长时间序列建模和预测的低复杂度金字塔注意力《PYRAFORMER: LOW-COMPLEXITY PYRAMIDAL ATTENTION FOR LONG-RANGE TIME SERIES MODELING AND FORECASTING》(金字塔注意力模块机制、PAM、CSCM、多尺度)

    今天是2022年10月1日,今天重读一遍这篇论文. 10月1日16:48,上次读是4月20日,时间过得好快. 论文:PYRAFORMER: LOW-COMPLEXITY PYRAMIDAL ATTEN ...

  4. Yarn 3.0 Plug'n'Play (PnP) 安装和迁移

    前言 以前用 npm, 后来 yarn 火了就用 yarn. 后来 yarn 2.0 大改版, Angular 不支持就一直没用. 一直到去年的 Angular 13 才开始支持. 最近又开始写 An ...

  5. Powershell 重新排列 Windows环境变量

    最近乱搞环境变量,然后有些重复了,遂写个脚本去重下排序下. 环境变量有长度限制,如果超出了,比如SqlServer相关的,将共同路径单独搞个变量声明下,比如 将其路径手动替换成如下,可大幅压缩变量长度 ...

  6. Response状态码

    1.数据是否正常 2.文件是否存在 3.地址自动跳转 4.服务提供错误 注:容错处理识别 •-1xx:指示信息-表示请求已接收,继续处理. •-2xx:成功-表示请求已经被成功接收.理解.接受. •- ...

  7. LeetCode 664. Strange Printer (DP)

    题目: 有台奇怪的打印机有以下两个特殊要求: 打印机每次只能打印同一个字符序列.每次可以在任意起始和结束位置打印新字符,并且会覆盖掉原来已有的字符.给定一个只包含小写英文字母的字符串,你的任务是计算这 ...

  8. Uefi ABL读取XBL设置的标志位

    PBL(启动固化程序)-> XBL(扩展引导加载程序,负责初始化芯片驱动和核心应用功能.XBL通常会加载一些平台相关的驱动程序,并提供通用接口)-> ABL(应用引导加载程序,负责引导操作 ...

  9. Java日期时间API系列14-----Jdk8中java.time包中的新的日期时间API类,java日期计算1,获取年月日时分秒等

    通过Java日期时间API系列8-----Jdk8中java.time包中的新的日期时间API类的LocalDate源码分析 ,可以看出java8设计非常好,实现接口Temporal, Tempora ...

  10. 怎么根据token的有⽆去控制路由的跳转?进度条跳转 - 白名单是否有token - 单独封装文件permission .js

    vue这边的路由⾃带了路由前置守卫,我们可以在前置守卫⾥拿到token数据,然后根据需求做分⽀判 断,要是token存在就使⽤next⽅法正常放⾏跳转,否则可以强制跳回到登录,让⽤户去获取token ...