1. 程式人生 > >【設計模式】之物件池模式--JDBC連線池簡單實現案例

【設計模式】之物件池模式--JDBC連線池簡單實現案例

文章目錄

物件池設計模式

原始碼Github地址戳這裡…

物件池設計模式的目標

物件池可以顯著提高效能,在那些初始化一個類例項的代價比較高、但是使用頻率比較低的場景時,物件池模式是非常高效的。

問題

物件池(資源池)常用於管理物件快取。一個客戶端通過物件池訪問已經存在的例項從而避免建立新的物件。
一般而言,物件池會持續生成物件,例如,如果物件池空了,則會建立新的物件。或者有一個物件池,限制了物件建立的數量。
如果想要保持物件的可重用性,最好將當前未使用的所有可重用物件儲存在同一個物件池中。

討論

物件池允許其它地方從其中“檢出”物件,當這些物件不再需要的時候,它們則會回收到物件池中以便重用。

不管怎樣,我們不希望一個執行緒必須等待獲得一個物件,所以物件池也會生成新的物件如果需要的話,但是必須實現定期清理不再使用的物件。

結構

連線池模式的一般思想是如果一個類的例項是可以重用的,你應該避免建立類的例項而是儘量重用它們。

物件池模式

  • Reusable 這個角色中的類例項與其他物件協作的時間是有限的,之後它們不再需要工作。

  • Client 這個角色中的類例項使用可重用的物件。

  • ReusablePool 這個角色中的類例項管理供Client使用的可重用物件。

通常,為了能夠保持所有的 Reusable物件在不使用的時候,可以儲存在同一個物件池中,這樣它們可以被統一管理起來。
為了達到這個目標, Reusable池類將被設計為一個單例類。其構造器是private修飾的,以強制客戶端呼叫它的 getInstance 方法去獲取一個物件例項。

一個 Client 物件需要一個 Reusable 物件的時候,可以呼叫一個 ReusablePool 物件的 acquireReusable 方法。
一個 ReusablePool 物件會維護一個 Reusable 物件的集合。
如果當 acquireReusable 方法被呼叫時,且存在 Reusable 物件在池中,則會從池中移除一個 Reusable 物件並且返回。如果 acquireReusable 方法不能建立新的 Reusable 物件,將會等待直到有一個 Reusable 物件返回到集合中。

當客戶端完成使用物件後,客戶端物件通過傳遞一個 Reusable 物件到 ReusablePool 物件的 releaseReusable 方法來釋放該物件。
releaseReusable 方法會返回一個 Reusable 物件到物件池中。

在很多存在物件池的應用中,可能存在這些原因限制 Reusable 物件的數量。在這種情況下,當數量不夠的時候,ReusablePool 物件可以建立新的 Reusable 物件,所以應該為其新增一個限制最大數量的方法例如 setMaxPoolSize。

示例

物件池模式示例

物件池模式類似於辦公室倉庫。當招聘了一個新的員工,辦公室經理必須為他準備一個工位。她想知道辦公室是否有多餘的辦公裝置,如果有的話,她就先使用,如果沒有,則先佔據一個購買新裝置的名額。
如果一個員工被解聘了,他的辦公裝置就會被轉移到倉庫中,當有新的需要時又會拿來使用。

在這裡插入圖片描述

核驗單

  • 1.建立一個 ObjectPool 類,內部包含一個 private 的物件陣列成員。
  • 2.在 ObjectPool 類中建立 acquire 和 release 方法。
  • 3.確保 ObjectPool 是單例的。

經驗法則

  • 工廠方法模式經常用於封裝物件的建立邏輯。然而,在它們建立後並沒有很好的管理起來,物件池模式則可以保持物件的跟蹤。
  • 物件池一版使用單例實現。

連線池模式示例程式碼

ObjectPool.java


package org.byron4j.cookbook.designpattern.objectpool;

import java.util.Enumeration;
import java.util.Hashtable;

/**
 * 物件池
 */
public abstract  class ObjectPool<T> {

    /**過期時間*/
    private int expirationTime;

    /**unlocked 是可用物件列表*/
    private Hashtable<T, Long> locked, unlocked;

    /**初始化預設超時時間、初始化可用物件列表、已鎖定列表*/
    public ObjectPool(){
        expirationTime = 30000;// 30 seconds
        locked = new Hashtable<>();
        unlocked = new Hashtable<>();
    }

    /**建立一個物件*/
    protected abstract T create();

    /**校驗物件是否還是有效的*/
    public abstract  boolean validate(T o);

    /**超時移除物件*/
    public abstract void expire(T o);

    /**
     * 從物件池中獲取一個可用物件
     *
     * */
    public synchronized  T checkout(){
        // 新的起始時間,當前有使用且有效,則延長其有效時間
        long now = System.currentTimeMillis();
        T t;

        if( unlocked.size() > 0 ){
            Enumeration<T> e = unlocked.keys();
            while( e.hasMoreElements() ){
                t = e.nextElement();
                if( (now - unlocked.get(t)) > expirationTime ){
                    // 存在時間過長了,移除物件
                    unlocked.remove(t);
                    // 且關閉連線
                    expire(t);
                    t = null;
                }else{
                    if( validate(t) ){
                        // 可用,則將其從可用列表中移除
                        unlocked.remove(t);
                        // 並且放入鎖定列表中,表示該物件已經被佔用了
                        locked.put(t, now);
                        // 返回該可用物件
                        return (t);
                    }else{
                        // 校驗不通過,則從可用列表中移除
                        unlocked.remove(t);
                        // 且關閉連線
                        expire(t);
                        t = null;
                    }
                }
            }
        }

        // 沒有找到可用的物件,則建立新的物件
        t = create();
        // 將新物件放入鎖定列表中
        locked.put(t, now);
        // 返回新物件
        return (t);
    }


    /**
     * 歸還物件
     * @param t
     */
    public synchronized void checkin(T t){
        // 將物件從鎖定列表中移除
        locked.remove(t);
        // 將物件加入到可用列表中,時間從當前開始
        unlocked.put(t, System.currentTimeMillis());
    }


}

JDBCConnectionPool.java


package org.byron4j.cookbook.designpattern.objectpool;

import java.sql.Connection;
import java.sql.DriverManager;

public class JDBCConnectionPool extends ObjectPool<Connection> {

    private String dsn, username, pwd;

    public JDBCConnectionPool(String driver, String dsn, String username, String pwd) {
        super();
        System.out.println("獲取JDBC連線池.");
        try{
            Class.forName(driver).newInstance();
        }catch (Exception e){
            e.printStackTrace();;
        }

        this.dsn = dsn;
        this.username = username;
        this.pwd = pwd;
    }

    @Override
    protected Connection create() {
        System.out.println("建立新的連線.");
        try{
            return DriverManager.getConnection(dsn, username, pwd);
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public boolean validate(Connection o) {
        System.out.println("檢查連線是否有效.");
        try{
            return !o.isClosed();
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public void expire(Connection o) {
        System.out.println("關閉連線.");
        try{
            o.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}


ObjectPoolTest.java


package org.byron4j.cookbook.designpattern;

import org.byron4j.cookbook.designpattern.objectpool.JDBCConnectionPool;
import org.junit.Test;

import java.sql.Connection;

public class ObjectPoolTest {

    @Test
    public void test(){

        // 建立JDBC連線池
        JDBCConnectionPool pool = new JDBCConnectionPool(
                "com.mysql.jdbc.Driver", "jdbc:mysql://localhost/mypydb",
                "root", "11111111");

        // 從連線池中獲取一個連線
        Connection con = pool.checkout();


        // 連接回收
        pool.checkin(con);
    }
}


JDBC連線池實現總結

  • 物件池建立並維護 可用物件列表鎖定物件列表(此例實現使用 Hashtable); 可用物件列表中地物件都可以使用,鎖定列表中地物件表示已經有執行緒在使用了當前不可用。
  • 當可用列表中沒有物件時,可以選擇新建物件以應對客戶端的請求,並將新物件放入鎖定列表中。
  • 可用列表中存在可用物件,則判斷其是否可用,可用則當如鎖定列表中,並返回該物件。
  • 使用完物件後,注意將該物件回收到池中,從鎖定列表中移除,放入可用列表中且更新有效時間。