当我们使用数据库连接池时,如果多线程连接数据库时,当超过5个线程连接数据库时,那么在第6个线程包括之后就连接不到数据库了,该怎么处理。
这里用了java的动态代理来代理数据库的连接,拦截连接的close方法。并且给代理的连接加上一个时间属性,和实时监控的线程。初始化5个连接放到栈里当做数据库连接池。当连接执行数据库操作时,则去调用真正的连接方法,且当连接用完时,将连接的时间清零,放回连接池里。如果大于5个连接数据库时,连接池没有连接时,则创建新的连接放进连接池里,当连接关闭时,如果连接池里已有5个连接,多余的连接如果超过10秒没被使用,则关闭连接。
由于因为连接池的数量发生变化时,要去重新创建新的连接,所以这里使用了观察者模式,创建观察者和被观察者。当连接池的数量为空时,则就通知观察者去重新创建新的连接。
当然为了在多线程环境下,防止自己的连接被其它线程篡改,导致线程不安全,这里使用了ThreadLocal。
threadlocal是一个数据结构,有点像HashMap,可以保存"key : value"键值对,但是一个ThreadLocal只能保存一个,并且各个线程的数据互不干扰。使用threadlocal将连接作为对象放到threadloacal里,实现只有该线程自己可以访问这个连接。
代码如下:
package pool;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Observable;
import java.util.Observer;
import java.util.Stack;
//继承Observable接口来实现观察者模式
public class PoolServer extends Observable {
// 创建数据库连接池
private Stack<ConnProxy> pool = new Stack<ConnProxy>();
/*
* threadlocal是一个数据结构,有点像HashMap,可以保存"key : value"键值对,
* 但是一个ThreadLocal只能保存一个,并且各个线程的数据互不干扰。
* 这里使用threadlocal将连接作为对象放到threadloacal里,实现只有该线程自己可以访问这个连接。
*/
private ThreadLocal<ConnProxy> threadLocal = new ThreadLocal<ConnProxy>();
// 在构造器初始化五个连接
public PoolServer() {
// 设置观察者实现Observer接口的唯一方法update
this.addObserver(new Observer() {
public void update(Observable o, Object arg) {
try {
for (int i = 0; i < 5; i++) {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/dvdstore", "root",
"root");
ConnProxy connProxy = new ConnProxy();
connProxy.setConn(conn);
pool.push(connProxy);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
this.setChanged();
this.notifyObservers();
}
//代理连接类
public class ConnProxy {
// 连接属性
private Connection conn;
private int idle;// 时间
public void setIdle(int idle) {
this.idle = idle;
}
public void setConn(Connection conn) {
this.conn = conn;
}
public Connection getConn() {
return conn;
}
public ConnProxy() {
// 设置空闲时间,如果空闲时间超过10秒,则回收
new Thread(new Runnable() {
public void run() {
try {
while (true) {
Thread.sleep(1000);
idle += 1000;
if (idle > 100000) {
synchronized (Object.class) {
if (pool.size() > 5) {
conn.close();
pool.remove(ConnProxy.this);
break;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
// 代理连接方法
public Connection getConn() {
return (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), new Class[] { Connection.class },
new InvocationHandler() {
public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
// 判断threadlocal是否有连接对象
if (threadLocal.get() == null) {
Q: while (true) {
// 监控连接池是不是空,如果不为空,则将连接放到threadlocal
synchronized (Object.class) {
while (!pool.isEmpty()) {
threadLocal.set(pool.pop());
break Q;
}
}
// 设置被观察者,监控到空时,则去通知观察者创建新的连接
while (pool.isEmpty()) {
setChanged();
notifyObservers();
break;
}
}
}
// 获取代理连接
ConnProxy p = threadLocal.get();
if (method.getName().equals("close")) {
p.setIdle(0);// 被使用过的idle从0开始
pool.push(p);
return null;// 不让其调用真正的close方法
} else {
Connection conn = p.getConn();
return method.invoke(conn, args);// 调用其真正的connetion方法
}
}
});
}
// 测试
public static void main(String[] args) throws Exception {
final PoolServer poolServer = new PoolServer();
// Thread.sleep(1000);
for (int i = 0; i < 12; i++) {
new Thread(new Runnable() {
public void run() {
try {
Connection conn = poolServer.getConn();
conn.prepareStatement("select * from bird");
System.out.println(conn);
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
System.in.read();
}
}
代码分析:
一开始创建poolserver对象时会初始化五个代理连接放到连接池里,并给连接加上时间属性,检测是否为空闲连接。当连接池的数量超过5个且空闲时间超过10秒则关闭连接。
当调用getConn的方法的时候,会去代理连接对象,当第一次获取连接时,threadlocal为空,进入while循环,判断连接池是否为空,如果不为空,则将连接对象放进threadlocal,跳出循环,如果判断为空,通知观察者创建新的连接。然后获取代理连接,拦截close方法,如果不是执行close方法则去调用真正的连接方法,当执行close方法时,就将连接的时间属性设置为0,将连接放回连接池。
这就是使用动态代理和观察者模式+threadlocal实现数据库连接池连接池的实现过程。