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