20

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

  1. 创建一个ListHashMap
  2. 创建预定义的连接数
  3. 将它们添加到集合中。
  4. 现在,当调用类的ConnectionImpl getConnection()方法时,ConnectionPoolingImpl返回一个连接引用。

现在,当有人返回连接 ( releaseConnection(ConnectionImpl O)) 时,我如何确保当同一个应用程序再次尝试重用连接对象时,我的实现会引发异常?

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

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

你对这种方法有什么看法?

4

4 回答 4

25

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

假设您有一个非常简单的连接,您可以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看起来像这样的 s 池(同样,忽略异常、并发等):

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,这种方法有几个优点:

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

防止恶意/错误使用——客户端只能返回他们从池中检索到的资源;他们无法关闭底层连接;他们不能使用他们已经返回的连接......等等。

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

于 2011-07-06T11:54:12.870 回答
6

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

于 2011-07-06T11:56:24.250 回答
3

连接池实现

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();
    }
}
于 2018-05-27T07:40:42.273 回答
0

好的,所以如果我理解正确,您的问题基本上是“我们如何确保线程不会返回与池的连接然后继续使用它?”。如果您不将“原始” Connection 对象传回给调用者,那么答案基本上是“如果需要,您可以在某处放置一些控制”。

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

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

在许多情况下,您也不必费心进行此检查。您正在向调用者传递一种访问数据库的方法,所以这有点像试图通过在机场扫描飞机的爆炸物来阻止飞行员将飞机撞向建筑物:他们都准备好弄乱您的系统,无论您是否进行额外检查。

于 2011-07-06T12:22:42.827 回答