首先看一下连接池的定义。它通过构造函数初始化连接的最大上限,通过一个双向队列来维护连接,调用方需要先调用fetchConnection(long)方法来指定在多少毫秒内超时获取连接,当连接使用完成后,需要调用releaseConnection(Connection)方法将连接放回线程池
public class ConnectionPool {
private LinkedList<Connection> pool = new LinkedList<Connection>();
/**
* 初始化连接池的大小
* @param initialSize
*/
public ConnectionPool(int initialSize) {
if (initialSize > 0) {
for (int i = 0; i < initialSize; i++) {
pool.addLast(ConnectionDriver.createConnection());
}
}
}
/**
* 释放连接,放回到连接池
* @param connection
*/
public void releaseConnection(Connection connection){
if(connection != null){
synchronized (pool) {
pool.addLast(connection);
pool.notifyAll();
}
}
}
/**
* 在mills内无法获取到连接,将会返回null
* @param mills
* @return
* @throws InterruptedException
*/
public Connection fetchConnection(long mills) throws InterruptedException{
synchronized (pool) {
if (mills <= 0) {
while (pool.isEmpty()) {
pool.wait();
}
return pool.removeFirst();
}else{
long future = System.currentTimeMillis() + mills;
long remaining = mills;
while (pool.isEmpty() && remaining > 0) {
pool.wait(remaining);
remaining = future - System.currentTimeMillis();
}
Connection result = null;
if (!pool.isEmpty()) {
result = pool.removeFirst();
}
return result;
}
}
}
}
由于java.sql.Connection是一个接口,最终的实现是由数据库驱动提供方来实现的,考虑到只是个示例,我们通过动态代理构造了一个Connection,该Connection的代理实现仅仅是在commit()方法调用时休眠100毫秒
public class ConnectionDriver {
static class ConnectionHandler implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.equals("commit")){
TimeUnit.MILLISECONDS.sleep(100);
}
return null;
}
}
/**
* 创建一个Connection的代理,在commit时休眠100毫秒
* @return
*/
public static final Connection createConnection(){
return (Connection) Proxy.newProxyInstance(ConnectionDriver.class.getClassLoader(),
new Class[] { Connection.class },new ConnectionHandler());
}
}
下面通过一个示例来测试简易数据库连接池的工作情况,模拟客户端ConnectionRunner获取、使用、最后释放连接的过程,当它使用时连接将会增加获取到连接的数量,反之,将会增加未获取到连接的数量
public class ConnectionPoolTest {
static ConnectionPool pool = new ConnectionPool(10);
static CountDownLatch start = new CountDownLatch(1);
static CountDownLatch end;
public static void main(String[] args) {
int threadCount = 10;
end = new CountDownLatch(threadCount);
int count = 20;
AtomicInteger got = new AtomicInteger();
AtomicInteger notGot = new AtomicInteger();
for (int i = 0; i < threadCount; i++) {
Thread thread = new Thread(new ConnetionRunner(count, got, notGot), "ConnectionRunnerThread");
thread.start();
}
start.countDown();
try {
end.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("total invoke: " + (threadCount * count));
System.out.println("got connection: " + got);
System.out.println("not got connection " + notGot);
}
static class ConnetionRunner implements Runnable {
int count;
AtomicInteger got;
AtomicInteger notGot;
public ConnetionRunner(int count, AtomicInteger got, AtomicInteger notGot) {
this.count = count;
this.got = got;
this.notGot = notGot;
}
@Override
public void run() {
try {
start.await();
} catch (Exception ex) {
}
while (count > 0) {
try {
Connection connection = pool.fetchConnection(1);
if (connection != null) {
try {
connection.createStatement();
connection.commit();
} finally {
pool.releaseConnection(connection);
got.incrementAndGet();
}
} else {
notGot.incrementAndGet();
}
} catch (Exception ex) {
} finally {
count--;
}
}
end.countDown();
}
}
}
CountDownLatch类是一个同步计数器,构造时传入int参数,该参数就是计数器的初始值,每调用一次countDown()方法,计数器减1,计数器大于0 时,await()方法会阻塞程序继续执行CountDownLatch如其所写,是一个倒计数的锁存器,当计数减至0时触发特定的事件。利用这种特性,可以让主线程等待子线程的结束。这这里保证让所有的ConnetionRunner
都执行完再执行main进行打印。
运行结果:
20个客户端
total invoke: 200
got connection: 200
not got connection 0
50个客户端
total invoke: 1000
got connection: 999
not got connection 1
100个客户端
total invoke: 2000
got connection: 1842
not got connection 158
在资源一定的情况下(连接池中的10个连接),随着客户端线程的逐步增加,客户端出现超时无法获取连接的比率不断升高。虽然客户端线程在这种超时获取的模式下会出现连接无法获取的情况,但是它能够保证客户端线程不会一直挂在连接获取的操作上,而是“按时”返回,并告知客户端连接获取出现问题,是系统的一种自我保护机制。数据库连接池的设计也可以复用到其他的资源获取的场景,针对昂贵资源(比如数据库连接)的获取都应该加以超时限制。