实现连接池:Java

时间:2011-07-06 10:59:57

标签: java connection pooling

在我面临的一次采访中,我被要求实施连接池。 所以方法是这样的:

  1. 创建ListHashMap
  2. 创建预定义的连接数
  3. 将它们添加到集合中。
  4. 现在,当调用ConnectionImpl类的getConnection() ConnectionPoolingImpl方法时,返回连接引用。
  5. 现在当有人返回连接(releaseConnection(ConnectionImpl O))时,如何确保当同一个应用程序再次尝试重用连接对象时,我的实现会抛出异常?

    可能已将相同的连接对象返回到新应用程序,并且应该能够使用它。

    我的观点是在每个Connectionimpl对象的另一个数组类型的结构中维护一个标志变量,并将该变量设置为有效值。当用户返回连接对象时,我会将其设为无效值。对于ConnectionImpl中的每个操作,我都必须验证用户是否有有效标记。

    你会对这种方法说些什么?

4 个答案:

答案 0 :(得分:19)

我不会从池中返回“真正的”连接对象,而是一个包装器,它提供控制连接生命周期,而不是客户端。

假设您有一个非常简单的连接,您可以从中读取int值:

interface Connection {
    int read(); // reads an int from the connection
    void close(); // closes the connection
}

从流中读取的实现可能如下所示(忽略异常,EOF处理等):

class StreamConnection implements Connection {
    private final InputStream input;
    int read(){ return input.read(); }
    void close(){ input.close(); }
}

此外,假设您有一个StreamConnection的池,看起来像这样(再次忽略异常,并发等):

class StreamConnectionPool {
    List<StreamConnection> freeConnections = openSomeConnectionsSomehow();
    StreamConnection borrowConnection(){ 
        if (freeConnections.isEmpty()) throw new IllegalStateException("No free connections");
        return freeConnections.remove(0); 
    }
    void returnConnection(StreamConnection conn){
        freeConnections.add(conn);
    }
}

这里的基本想法没问题,但我们无法确定连接是否已返回,我们无法确定它们是否已关闭然后返回,或者您没有返回来自的连接另一个来源。

解决方案是(当然)另一层间接:创建一个返回包装器Connection的池,而不是在调用close()时关闭底层连接,将其返回到池:

class ConnectionPool {

    private final StreamConnectionPool streamPool = ...;

    Connection getConnection() {
        final StreamConnection realConnection = streamPool.borrowConnection();
        return new Connection(){
            private boolean closed = false;
            int read () {
                if (closed) throw new IllegalStateException("Connection closed"); 
                return realConnection.read();
            }
            void close() {
                if (!closed) {
                    closed = true;
                    streamPool.returnConnection(realConnection);
                }
            }
            protected void finalize() throws Throwable {
                try {
                    close();
                } finally {
                    super.finalize();
                }
            }
        };
    }

}

ConnectionPool将是客户端代码唯一看到的内容。假设它是StreamConnectionPool的唯一所有者,这种方法有几个优点:

降低复杂性并降低对客户端代码的影响 - 自己打开连接和使用池之间的唯一区别是您使用工厂来获取Connection s(您可能会已经做了,如果你正在使用依赖注入)。最重要的是,您始终以相同的方式清理资源,即通过调用close()。就像你不关心read做什么一样,只要它给你提供你需要的数据,你就不关心close()做什么,只要它释放你声称的资源。您不必考虑此连接是否来自池。

防止恶意/不正确使用 - 客户端只能返回他们从池中检索到的资源;他们无法关闭基础联系;他们不能使用他们已经返回的连接......等等。

“保证”返回资源 - 由于我们的finalize实施,即使所有对借用Connection的引用都丢失,它仍会返回到池中(或者至少有机会被退回)。当然,连接将以这种方式保持不必要的时间 - 可能是无限期的,因为最终确定无法保证 - 但这只是一个很小的改进。

答案 1 :(得分:5)

我只是告诉他们我会使用H2附带的JdbcConnectionPool类(here)(您可以将其复制出来)。螺丝试图实现一个:)这可能是一个技巧问题。

答案 2 :(得分:0)

好的,所以如果我理解正确,你的问题基本上是“我们怎样才能确保线程不会返回到池的连接然后继续使用它?”。如果您没有将“原始”Connection对象传回给调用者,那么答案基本上是“如果您愿意,可以在某个地方放置一些控件”。

实际检查可能涉及在给定时刻标记Thread“拥有”它的每个连接,然后在任何使用连接的调用期间确保它始终是Thread.currentThread()。

将你传递给连接用户的对象传递回来表示连接并不重要:它可能是你自己的Connection包装器实现,或者只是一些其他包装器对象和你的执行查询的方法。无论您使用哪种,只需在执行任何查询之前进行上述检查。请记住,为了安全起见,通常不应该允许执行“原始”任意SQL,而是所有查询都应该基于定义良好的PreparedStatement。因此,没有特别的强制要求返回Connection的实际实现,除此之外,在某些情况下可能会帮助您迁移现有代码(和/或如果您确定您确实希望允许执行任意SQL)。

在许多情况下,你也可以不打扰这个检查。您正在通过呼叫者访问您的数据库的方法,所以这有点像试图阻止飞行员通过在机场扫描爆炸物使飞机撞毁建筑物:他们都准备好了搞乱您的系统,无论您是否进行额外检查。

答案 3 :(得分:0)

ConnectionPool implemenation

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;



/** A Connection Pool with 5 Available Connections **/
class ConnectionPool {

    private List<Connection>availableConnections = 
                                new ArrayList<Connection>();
    private List<Connection>usedConnections = new ArrayList<Connection>();
    private final int MAX_CONNECTIONS = 5;

    private String URL;
    private String USERID;
    private String PASSWORD;


    /** Initialize all 5 Connections and put them in the Pool **/
    public ConnectionPool(String Url, String UserId, String password)
            throws SQLException {
        this.URL = Url;
        this.USERID = UserId;
        this.PASSWORD = password;

        for (int count = 0; count <MAX_CONNECTIONS; count++) {
            availableConnections.add(this.createConnection());
        }

    }






/** Private function, 
    used by the Pool to create new connection internally **/

    private Connection createConnection() throws SQLException {
        return DriverManager
                .getConnection(this.URL, this.USERID, this.PASSWORD);
    }




    /** Public function, used by us to get connection from Pool **/
    public Connection getConnection() {
        if (availableConnections.size() == 0) {
            System.out.println("All connections are Used !!");
            return null;
        } else {
            Connection con = 
            availableConnections.remove(
                availableConnections.size() - 1);
            usedConnections.add(con);
            return con;
        }
    }



    /** Public function, to return connection back to the Pool **/
    public boolean releaseConnection(Connection con) {
        if (null != con) {
            usedConnections.remove(con);
            availableConnections.add(con);
            return true;
        }
        return false;
    }





    /** Utility function to check the number of Available Connections **/
    public int getFreeConnectionCount() {
        return availableConnections.size();
    }
}