1. 程式人生 > >Java 使用動態代理和觀察者模式+threadlocal實現資料庫連線池

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實現資料庫連線池連線池的實現過程。