实现连接池:Java

发布于 2024-11-18 23:10:30 字数 581 浏览 1 评论 0原文

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

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

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

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

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

您对这种做法有何看法?

In one of the interviews that I faced,I was asked to implement connection pooling.
So approach was this:

  1. Create a List or HashMap
  2. Create predefined number of connections
  3. Add them to the collection.
  4. Now when the ConnectionImpl getConnection() method of ConnectionPoolingImpl class is invoked return a connection reference.

Now when someone returns the connection (releaseConnection(ConnectionImpl O)) how can I ensure that when the same application again tries to reuse the connection object, my implementation throws an exception?

The same connection object might have been returned to a new application and that should be able to use it.

My point of view would be to maintain a flag variable in another array kind of structure for each Connectionimpl object and set that variable to a valid value. When user returns the connection object I would make that some invalid value. For every operation in my ConnectionImpl, I will have to verify if the user had a valid flag.

What would you say to that approach?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

海夕 2024-11-25 23:10:30

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

假设您有一个非常简单的连接,您可以从中读取 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 的唯一所有者,此方法有几个优点:

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

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

“保证”返回资源 - 得益于我们的finalize实现,即使所有引用都是借用的连接丢失,它仍然返回到池中(或者至少有机会返回)。通过这种方式,连接的保持时间当然会比必要的时间长——可能是无限期的,因为最终确定不能保证永远运行——但这只是一个小小的改进。

I would not return the "real" connection object from the pool, but a wrapper which gives the pool control of connection life cycle, instead of the client.

Assume you have a really simple connection, which you can read int values from:

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

An implementation reading from a stream could look like this (ignoring exceptions, EOF handling, etc):

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

Furthermore, let's assume you have a pool for StreamConnections that looks like this (again, ignoring exceptions, concurrency, etc):

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);
    }
}

The basic idea here is OK, but we can't be sure the connections are returned, and we can't be sure they aren't closed and then returned, or that you don't return a connection which came from another source altogether.

The solution is (of course) another layer of indirection: Make a pool which returns a wrapper Connection which, instead of closing the underlying connection when close() is called, returns it to the pool:

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();
                }
            }
        };
    }

}

This ConnectionPool would be the only thing the client code ever sees. Assuming it is the sole owner of the StreamConnectionPool, this approach has several advantages:

Reduced complexity and minimal impact on client code - the only difference between opening connections yourself and using the pool is that you use a factory to get hold of Connections (which you might already do, if you're using dependency injection). Most importantly, you always clean up your resources in the same way, i.e., by calling close(). Just like you don't care what read does, as long as it gives you the data you need, you don't care what close() does, as long as it releases the resources you've claimed. You shouldn't have to think whether this connection is from a pool or not.

Protection against malicious/incorrect usage - clients can only return resources they've retrieved from the pool; they can't close the underlying connections; they can't use connections they've already returned... etc.

"Guaranteed" returning of resources - thanks to our finalize implementation, even if all references to a borrowed Connection is lost, it is still returned to the pool (or does at least stand a chance to be returned). The connection will of course be held longer than necessary this way - possibly indefinitely, since finalization isn't guaranteed to ever run - but it's a small improvement.

明明#如月 2024-11-25 23:10:30

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

I'd just tell them I'd use the JdbcConnectionPool class (here) that comes with H2 (you can probably copy it out). Screw trying to implement one :) It could be a trick question.

葬﹪忆之殇 2024-11-25 23:10:30

连接池实现

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();
    }
}

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();
    }
}
拿命拼未来 2024-11-25 23:10:30

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

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

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

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

OK, so if I understand correctly, your question is basically "how can we can ensure that a thread doesn't return a connection to the pool and then carry on using it?". Provided you don't pass back the "raw" Connection object to the caller, then the answer is essentially "you can put some control in somewhere if you want".

The actual check could involve marking each connection with which Thread "owns" it at a given moment, then making sure this is always Thread.currentThread() during any call to use the connection.

It doesn't matter terribly much what object you do pass back to user of the connection to represent the connection: it could be your own wrapper implementation of Connection, or just some other wrapper object with your methods for executing queries. Whichever you use, you just need to make the abovementioned check before executing any query. Bear in mind that for security you generally shouldn't be allowing "raw" arbitrary SQL to be executed, but that all queries should be based on a well-defined PreparedStatement. So there's no particular compulsion to return an actual implementation of Connection, other than this might in some circumstances help you migrate existing code (and/or if you've decided you really do want to permit execution of arbitrary SQL).

In many circumstances, you could also not bother making this check. You're passing a caller a means to access your database, so it's a bit like trying to stop pilots from crashing planes into buildings by scanning them for explosives at airports: they all ready have a means of messing up your system whether or not you make the additional check.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文