Java 使用動態代理和觀察者模式+threadlocal實現資料庫連線池
當我們使用資料庫連線池時,如果多執行緒連線資料庫時,當超過5個執行緒連線資料庫時,那麼在第6個執行緒包括之後就連線不到資料庫了,該怎麼處理。 這裡用了java動態代理來這裡用了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實現資料庫連線池連線池的實現過程。