Я бы не возвращал «настоящий» объект соединения из пула, а обертку, которая дает пулу контроль над жизненным циклом соединения, а не клиенту.
Предположим, у вас действительно простое соединение, из которого вы можете считывать значения 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
(что вы, возможно, уже сделали, если используете внедрение зависимости). Самое главное, вы всегда очищаете свои ресурсы одним и тем же способом, т. е. вызывая close()
. Точно так же, как вам все равно, что делает read
, пока он дает вам нужные данные, вас не волнует, что делает close()
, пока он освобождает ресурсы, которые вы потребовали. Вам не нужно думать, является ли это соединение из пула или нет.
Защита от злонамеренного/неправильного использования — клиенты могут возвращать только те ресурсы, которые они извлекли из пула; они не могут закрыть базовые соединения; они не могут использовать соединения, которые они уже вернули... и т.д.
"Гарантированный" возврат ресурсов - благодаря нашей реализации finalize
, даже если все ссылки на заимствованный Connection
потеряны, он все равно возвращается в пул (или, по крайней мере, имеет шанс быть возвращенным) . Таким образом, конечно, соединение будет удерживаться дольше, чем необходимо - возможно, бесконечно, поскольку финализация не гарантируется когда-либо, - но это небольшое улучшение.
person
gustafc
schedule
06.07.2011