目录
1、对象池模式的定义
- 1.1、先来看看百度百科的定义
- 1.2、 对象池模式就是单例模式加享元模式
2、为什么要用对象池模式
- 2.1、原因
- 2.2、解决方案
3、对象池模式示例代码
4、对象池模式的应用场景
5、对象池模式的优缺点
- 5.1、优点
- 5.2、缺点
6、结语
1、对象池模式的定义
1.1、先来看看百度百科的定义
对象池模式 (The Object Pool Pattern) 是单例模式的一个变种,它提供了获取一系列相同对象实例的入口。当你需要对象来代表一组可替代资源的时候就变的很有用,每个对象每次可以被一个组件使用。
1.2、 对象池模式就是单例模式加享元模式
对象池模式(Object Pool Pattern),是创建型设计模式的一种,将对象预先创建并初始化后放入对象池中,对象提供者就能利用已有的对象来处理请求,减少频繁创建对象所占用的内存空间和初始化时间。一个对象池包含一组已经初始化并且可以使用的对象,可以在有需求时创建和销毁对象。对象池的用户可以从池子中取得对象,对其进行操作处理,并在不需要时归还给池子而非直接销毁。对象池是一个特殊的工厂对象,对象池模式就是单例模式加享元模式。
对象池模式和享元模式的最大区别在于:
对象池模式中会多一个回收对象重复利用的方法。所以,对象池模式应该是享元模式更加具体的一个应用场景。相当于先将对象从对象池中借出,用完之后再还回去,以此保证有限资源的重复利用。
了解了对象池模式的定义,接下来,咱们就该思考为什么要用对象池模式。 用对象池模式的好处是什么以及它的弊端。
2、为什么要用对象池模式
2.1、原因
我们都知道对象的实例化是最耗性能的操作之一,这在过去是个大问题,现在不用再过分关注它。但是当我们处理封装外部资源的对象(例如数据库连接)时,对象的创建操作则会耗费很多资源。
2.2、解决方案
解决方案就是重用和共享这些创建成本高昂的对象,这就是我们讲到的对象池模式。先来看看它的类图结构,如下图 2-1。
图 2-1
对象池模式中使用的类如下所示:
ResourcePool (资源池类):用于封装逻辑的类。用来保存和管理资源列表。
Resource(资源类):用于封装特定资源的类。资源类通常被资源池类引用,因此只要资源池不重新分配,它们就永远不会被回收。
Client(客户端类):使用资源的类。
当客户需要新资源时,会向资源池类申请,资源池类检查后获取第一个可用资源并将其返回给客户端。
客户端使用完资源后会进行资源释放,资源会重新回到资源池以便重复使用。
3、对象池模式示例代码
ObjectPool.java 文件代码如下:
package com.zhaoyanfei.designpattern.objectPoolPattern;
import java.util.Enumeration;
import java.util.Vector;
/**
* 对象池
* @Date 2022年10月4日
* @author zhaoYanFei
*
*/
public class ObjectPool {
private int numObjects = 10; // 对象池的大小
private int maxObjects = 50; // 对象池最大的大小
private Vector<PooledObject> objects = null; //存放对象池中对象的向量(PooledObject类型)
public ObjectPool() {
}
/*** 创建一个对象池***/
public synchronized void createPool(){
// 确保对象池没有创建。如果创建了,保存对象的向量 objects 不会为空
if (objects != null) {
return; // 如果己经创建,则返回
}
// 创建保存对象的向量 , 初始时有 0 个元素
objects = new Vector<PooledObject>();
// 根据 numObjects 中设置的值,循环创建指定数目的对象
for (int x = 0; x < numObjects; x++) {
if ((objects.size() == 0)&&this.objects.size() <this.maxObjects) {
Object obj = new Object();
objects.addElement(new PooledObject(obj));
}
}
}
/**
* 获取对象
* @return
*/
public synchronized Object getObject(){
// 确保对象池己被创建
if (objects == null) {
return null; // 对象池还没创建,则返回 null
}
Object conn = getFreeObject(); // 获得一个可用的对象
// 如果目前没有可以使用的对象,即所有的对象都在使用中
while (conn == null) {
wait(250);
conn = getFreeObject(); // 重新再试,直到获得可用的对象,如果
// getFreeObject() 返回的为 null,则表明创建一批对象后也不可获得可用对象
}
return conn;// 返回获得的可用的对象
}
/**
* 本函数从对象池对象 objects 中返回一个可用的的对象
* 如果当前没有可用的对象,则创建几个对象,并放入对象池中。
* 如果创建后,所有的对象都在使用中,则返回 null
*/
private Object getFreeObject(){
// 从对象池中获得一个可用的对象
Object obj = findFreeObject();
if (obj == null) {
createPool();//如果目前对象池中没有可用的对象,创建一些对象
// 重新从池中查找是否有可用对象
obj = findFreeObject();
// 如果创建对象后仍获得不到可用的对象,则返回 null
if (obj == null) {
return null;
}
}
return obj;
}
/**
* 查找对象池中所有的对象,查找一个可用的对象,
* 如果没有可用的对象,返回 null
*/
private Object findFreeObject(){
Object obj = null;
PooledObject pObj = null;
// 获得对象池向量中所有的对象
Enumeration enumerate = objects.elements();
// 遍历所有的对象,看是否有可用的对象
while (enumerate.hasMoreElements()) {
pObj = (PooledObject) enumerate.nextElement();
// 如果此对象不忙,则获得它的对象并把它设为忙
if (!pObj.isBusy()) {
obj = pObj.getObject();
pObj.setBusy(true);
}
}
return obj;// 返回找到到的可用对象
}
/**
* 此函数返回一个对象到对象池中,并把此对象置为空闲。
* 所有使用对象池获得的对象均应在不使用此对象时返回它。
*/
public void returnObject(Object obj) {
// 确保对象池存在,如果对象没有创建(不存在),直接返回
if (objects == null) {
return;
}
PooledObject pObj = null;
Enumeration enumerate = objects.elements();
// 遍历对象池中的所有对象,找到这个要返回的对象
while (enumerate.hasMoreElements()) {
pObj = (PooledObject) enumerate.nextElement();
// 先找到对象池中的要返回的对象
if (obj == pObj.getObject()) {
// 找到了 , 设置此对象为空闲状态
pObj.setBusy(false);
break;
}
}
}
/**
* 关闭对象池中所有的对象,并清空对象池。
*/
public synchronized void closeObjectPool() {
// 确保对象池存在,如果不存在,返回
if (objects == null) {
return;
}
PooledObject pObj = null;
Enumeration enumerate = objects.elements();
while (enumerate.hasMoreElements()) {
pObj = (PooledObject) enumerate.nextElement();
// 如果忙,等 5 秒
if (pObj.isBusy()) {
wait(5000); // 等 5 秒
}
// 从对象池向量中删除它
objects.removeElement(pObj);
}
// 置对象池为空
objects = null;
}
/**
* 使程序等待给定的毫秒数
*/
private void wait(int mSeconds) {
try {
Thread.sleep(mSeconds);
} catch (InterruptedException e) {
}
}
/**
* 内部使用的用于保存对象池中对象的类。
* 此类中有两个成员,一个是对象,另一个是指示此对象是否正在使用的标志 。
*/
class PooledObject {
Object objection = null;// 对象
boolean busy = false; // 此对象是否正在使用的标志,默认没有正在使用
// 构造函数,根据一个 Object 构告一个 PooledObject 对象
public PooledObject(Object objection) {
this.objection = objection;
}
// 返回此对象中的对象
public Object getObject() {
return objection;
}
// 设置此对象的,对象
public void setObject(Object objection) {
this.objection = objection;
}
// 获得对象对象是否忙
public boolean isBusy() {
return busy;
}
// 设置对象的对象正在忙
public void setBusy(boolean busy) {
this.busy = busy;
}
}
}
ObjectPoolTest.java 对象池测试用例代码如下:
package com.zhaoyanfei.designpattern.objectPoolPattern;
/**
* 对象池测试类
* @Date 2022年10月4日
* @author zhaoYanFei
*
*/
public class ObjectPoolTest {
public static void main(String[] args) {
//创建对象池
ObjectPool objPool = new ObjectPool();
objPool.createPool();
//客户端请求对象池获取对象Object
Object obj = objPool.getObject();
/**
* TODO
* 此处拿到对象做一些具体的业务操作
*/
//对象用完后归还到对象池中,供其他客户端请求复用对象
objPool.returnObject(obj);
//最后这个操作是关闭对象池中所有对象,并清空对象池
objPool.closeObjectPool();
}
}
4、对象池模式的应用场景
对象池模式主要适用于以下应用场景。
(1)资源受限的场景。比如,不需要可伸缩性的环境(CPU\内存等物理资源有限),CPU性能不够强劲,内存比较紧张,垃圾收集,内存抖动会造成比较大的影响,需要提高内存管理效率, 响应性比吞吐量更为重要。
(2)在内存中数量受限的对象。
(3)创建成本高的对象,可以考虑池化。
补充:常见的使用对象池的场景有在使用Socket时的各种连接池、线程池、数据库连接池等。
5、对象池模式的优缺点
5.1、优点
复用池中对象,消除创建对象、回收对象所产生的内存开销、CPU开销,以及跨网络产生的网络开销。
5.2、缺点
(1)增加了分配 / 释放对象的开销。
(2)在并发环境中,多个线程可能(同时)需要获取池中对象,进而需要在堆数据结构上进行同步或者因为锁竞争而产生阻塞,这种开销要比创建销毁对象的开销高数百倍。
(3)由于池中对象的数量有限,势必成为一个可伸缩性瓶颈。
(4)很难合理设定对象池的大小,如果太小,则起不到作用;如果过大,则占用内存资源过高。
6、结语
对象池模式的整体设计思想就是:
当客户需要新资源时,会向资源池类申请,资源池类检查后获取第一个可用资源并将其返回给客户端。
客户端使用完资源后会进行资源释放,资源会重新回到资源池以便重复使用。
码字不易,看完之后,感觉对您还有些帮助的话,那就动动你发财的小手, 帮小编点个赞,顺便再帮小编点个免费的关注呀,我会持续为您更新设计模式相关的内容。
你的点赞就是对小编最大的支持与认可。
如果对博文有所疑问或者指正,还望评论区留言哦^_^。
今天就到这儿吧,下期我会逐步详细讲解设计模式中的行为型模式。
下期咱们不见不散……