对象池模式
源代码Git地址:https://gitee.com/zyxscuec/Design-pattern.git
文章目录
(1) 概念
Object Pool,即对象池,对象被预先创建并初始化后放入对象池中,对象提供者就能利用已有的对象来处理请求,减少对象频繁创建所占用的内存空间和初始化时间,例如数据库连接对象基本上都是创建后就被放入连接池中,后续的查询请求使用的是连接池中的对象,从而加快了查询速度。类似被放入对象池中的对象还包括Socket对象、线程对象和绘图对象(GDI对象)等。
在Object Pool设计模式中,主要有两个参与者:对象池的管理者和对象池的用户,用户从管理者那里获取对象,对象池对于用户来讲是透明的,但是用户必须遵守这些对象的使用规则,使用完对象后必须归还或者关闭对象,例如数据库连接对象使用完后必须关闭,否则该对象就会被一直占用着。
对象管理者需要维护对象池,包括初始化对象池、扩充对象池的大小、重置归还对象的状态等。
对象池在被初始化时,可能只有几个对象,甚至没有对象,按需创建对象能节省资源和时间,对于响应时间要求较高的情况,可以预先创建若干个对象。
当对象池中没有对象可供使用时,管理者一般需要使用某种策略来扩充对象池,比如将对象池的大小翻倍。另外,在多线程的情况下,可以让请求资源的线程等待,直到其他线程归还了占用的对象。
一般来说,对象池中的对象在逻辑状态上是相同的,如果都是无状态对象(即没有成员变量的对象),那么这些对象的管理会方便的多,否则,对象被使用后的状态重置工作就要由管理者来承担
(2)适用场景
对象池模式经常用在频繁创建、销毁对象(并且对象创建、销毁开销很大)的场景,比如数据库连接池、线程池、任务队列池等。
(3)代码示例
示例的整体类图:
- Connection接口
package com.alibaba.design.objectpoolpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-19:14 */ public interface Connection { Object get(); void set(Object x); }
- 实现接口的连接类ConnectionImplementation
package com.alibaba.design.objectpoolpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-19:14 */ public class ConnectionImplementation implements Connection { @Override public Object get() { return new Object(); } @Override public void set(Object x) { System.out.println("设置连接线程为: " + x); } }
连接池类ConnectionPool
package com.alibaba.design.objectpoolpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-19:15 */ public class ConnectionPool { //池管理对象 private static PoolManager pool = new PoolManager(); //指定连接数 并添加 public static void addConnections(int number) { for (int i = 0; i < number; i++) { pool.add(new ConnectionImplementation()); System.out.println("添加第 " + i + " 个连接资源"); } } //获取连接 public static Connection getConnection() throws PoolManager.EmptyPoolException { return (Connection) pool.get(); } //释放指定的连接 public static void releaseConnection(Connection c) { pool.release(c); System.out.println("释放整个连接资源: " + c); } }
连接池管理类PoolManager
package com.alibaba.design.objectpoolpattern; import java.util.ArrayList; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-19:12 */ public class PoolManager { //连接池对象 public static class PoolItem { boolean inUse = false; Object item; //池数据 PoolItem(Object item) { this.item = item; } } //连接池集合 private ArrayList items = new ArrayList(); public void add(Object item) { this.items.add(new PoolItem(item)); } static class EmptyPoolException extends Exception { } public Object get() throws EmptyPoolException { for (int i = 0; i < items.size(); i++) { PoolItem pitem = (PoolItem) items.get(i); if (pitem.inUse == false) { pitem.inUse = true; System.out.println("获取连接资源为: " + items.get(i)); return pitem.item; } } throw new EmptyPoolException(); // return null; } /** * 释放连接 * @param item */ public void release(Object item) { for (int i = 0; i < items.size(); i++) { PoolItem pitem = (PoolItem) items.get(i); if (item == pitem.item) { pitem.inUse = false; System.out.println("释放连接资源 : " + items.get(i)); return; } } throw new RuntimeException(item + " not null"); } }
客户端测试类
package com.alibaba.design.objectpoolpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-19:16 */ public class Test { static { ConnectionPool.addConnections(5); } public void test1() { Connection c; try { // 获得连接 c = ConnectionPool.getConnection(); } catch (PoolManager.EmptyPoolException e) { throw new RuntimeException(e); } // 设值 c.set(new Object()); //获取 c.get(); // 释放 ConnectionPool.releaseConnection(c); } public static void main(String args[]) { Test test = new Test(); test.test1(); } }
输出结果
(4)该模式在源码中的体现
javax.sql.PooledConnection
看它的注释
/** * An object that provides hooks for connection pool management. * A <code>PooledConnection</code> object * represents a physical connection to a data source. The connection * can be recycled rather than being closed when an application is * finished with it, thus reducing the number of connections that * need to be made. * <P> * An application programmer does not use the <code>PooledConnection</code> * interface directly; rather, it is used by a middle tier infrastructure * that manages the pooling of connections. * <P> * When an application calls the method <code>DataSource.getConnection</code>, * it gets back a <code>Connection</code> object. If connection pooling is * being done, that <code>Connection</code> object is actually a handle to * a <code>PooledConnection</code> object, which is a physical connection. * <P> * The connection pool manager, typically the application server, maintains * a pool of <code>PooledConnection</code> objects. If there is a * <code>PooledConnection</code> object available in the pool, the * connection pool manager returns a <code>Connection</code> object that * is a handle to that physical connection. * If no <code>PooledConnection</code> object is available, the * connection pool manager calls the <code>ConnectionPoolDataSource</code> * method <code>getPoolConnection</code> to create a new physical connection. The * JDBC driver implementing <code>ConnectionPoolDataSource</code> creates a * new <code>PooledConnection</code> object and returns a handle to it. * <P> * When an application closes a connection, it calls the <code>Connection</code> * method <code>close</code>. When connection pooling is being done, * the connection pool manager is notified because it has registered itself as * a <code>ConnectionEventListener</code> object using the * <code>ConnectionPool</code> method <code>addConnectionEventListener</code>. * The connection pool manager deactivates the handle to * the <code>PooledConnection</code> object and returns the * <code>PooledConnection</code> object to the pool of connections so that * it can be used again. Thus, when an application closes its connection, * the underlying physical connection is recycled rather than being closed. * <P> * The physical connection is not closed until the connection pool manager * calls the <code>PooledConnection</code> method <code>close</code>. * This method is generally called to have an orderly shutdown of the server or * if a fatal error has made the connection unusable. * * <p> * A connection pool manager is often also a statement pool manager, maintaining * a pool of <code>PreparedStatement</code> objects. * When an application closes a prepared statement, it calls the * <code>PreparedStatement</code> * method <code>close</code>. When <code>Statement</code> pooling is being done, * the pool manager is notified because it has registered itself as * a <code>StatementEventListener</code> object using the * <code>ConnectionPool</code> method <code>addStatementEventListener</code>. * Thus, when an application closes its <code>PreparedStatement</code>, * the underlying prepared statement is recycled rather than being closed. * <P> * * @since 1.4 */
/ ** *提供用于连接池管理的挂钩的对象。 * <code> PooledConnection </ code>对象 *表示与数据源的物理连接。连接 *可以回收而不是在应用程序关闭时关闭 *完成后,减少了连接数 *必须填写。 * <P> *应用程序程序员不使用<code> PooledConnection </ code> *直接接口;而是由中间层基础架构使用 *管理连接池。 * <P> *当应用程序调用方法<code> DataSource.getConnection </ code>时, *它返回一个<code> Connection </ code>对象。如果连接池是 *完成后,该<code> Connection </ code>对象实际上是 * <code> PooledConnection </ code>对象,它是物理连接。 * <P> *连接池管理器(通常是应用程序服务器)维护 * <code> PooledConnection </ code>对象的池。如果有 * <code> PooledConnection </ code>对象在池中可用, *连接池管理器返回一个<code> Connection </ code>对象,该对象 *是该物理连接的句柄。 *如果没有<code> PooledConnection </ code>对象可用,则 *连接池管理器调用<code> ConnectionPoolDataSource </ code> *方法<code> getPoolConnection </ code>创建一个新的物理连接。的 *实现<code> ConnectionPoolDataSource </ code>的JDBC驱动程序创建了一个 *新的<code> PooledConnection </ code>对象并返回其句柄。 * <P> *当应用程序关闭连接时,它会调用<code> Connection </ code> *方法<code> close </ code>。完成连接池后, *连接池管理器已被通知,因为它已将自己注册为 *使用<code> ConnectionEventListener </ code>对象 * <code> ConnectionPool </ code>方法<code> addConnectionEventListener </ code>。 *连接池管理器取消激活 * <code> PooledConnection </ code>对象并返回 * <code> PooledConnection </ code>对象到连接池,以便 *它可以再次使用。因此,当应用程序关闭其连接时, *基础物理连接被回收而不是关闭。 * <P> *直到连接池管理器才关闭物理连接 *调用<code> PooledConnection </ code>方法<code> close </ code>。 *此方法通常被称为有序关闭服务器或 *如果发生致命错误使连接无法使用。 * * <p> *连接池管理器通常也是语句池管理器,用于维护 * <code> PreparedStatement </ code>对象的池。 *当应用程序关闭准备好的语句时,它将调用 * <code> PreparedStatement </ code> *方法<code> close </ code>。完成<code> Statement </ code>池后, *通知池管理器,因为它已将自己注册为 *使用<code> StatementEventListener </ code>对象 * <code> ConnectionPool </ code>方法<code> addStatementEventListener </ code>。 *因此,当应用程序关闭其<code> PreparedStatement </ code>时, *基础的准备好的语句被回收而不是关闭。 * <P> * * @从1.4开始 * /
(5)对象池模式的优缺点
- 优点
复用池中对象,没有分配内存和创建堆中对象的开销, 没有释放内存和销毁堆中对象的开销, 进而减少垃圾收集器的负担, 避免内存抖动;不必重复初始化对象状态, 对于比较耗时的constructor和finalize来说非常合适; - 缺点
- Java的对象分配操作不比c语言的malloc调用慢, 对于轻中量级的对象, 分配/释放对象的开销可以忽略不计;
- 并发环境中, 多个线程可能(同时)需要获取池中对象, 进而需要在堆数据结构上进行同步或者因为锁竞争而产生阻塞, 这种开销要比创建销毁对象的开销高数百倍;
- 由于池中对象的数量有限, 势必成为一个可伸缩性瓶颈;
- 很难正确的设定对象池的大小, 如果太小则起不到作用, 如果过大, 则占用内存资源高, 可以起一个线程定期扫描分析, 将池压缩到一个合适的尺寸以节约内存,但为了获得不错的分析结果, 在扫描期间可能需要暂停复用以避免干扰(造成效率低下), 或者使用非常复杂的算法策略(增加维护难度);
- 设计和使用对象池容易出错, 设计上需要注意状态同步, 这是个难点, 使用上可能存在忘记归还(就像c语言编程忘记free一样), 重复归还(可能需要做个循环判断一下是否池中存在此对象, 这也是个开销), 归还后仍旧使用对象(可能造成多个线程并发使用一个对象的情况)等问题