併發實戰(1)- 模擬等待超時模式的連接池
前言
我們來進行併發的實戰,用等待超時模式來實現連接池的功能。
不管是在Spring還是Mybatis中的的連接池,都是按照等待超時的思想來實現的。
接下來簡單的來實現一個等待超時模式的連接池
什麼是等待超時模式的連接池
什麼是等待超時模式的連接池,從名字中可以看到就是通過等待與等待超時來實現一個連接池。連接池也就是一個池子容器,裏面放着我們定義好的連接,在需要的時候從中取出連接,並且在使用完成後歸還接連,減少了連接創建與銷燬的性能消耗。
所以我們想實現一個等待超時模式的連接池,需要實現以下幾步
一、創建一個連接池類
二、定義一個連接池容器
三、初始化連接池容器
四、定義取出連接方法
五、定義歸還連接方法
連接池的實現
/**
* @version 1.0
* @Description 數據庫連接池
* @Author 殘冬十九
* @Date 2020/6/15
*/
public class DbPool {
/**
* 數據庫連接池容器
*/
private final static LinkedList<Connection> CONNECTION_POOL = new LinkedList<>();
/**
* 初始化連接池,傳入初始化大小,當大於0會初始化連接池容器
*
* @param initialSize
*/
public DbPool(int initialSize) {
if (initialSize > 0) {
for (int i = 0; i < initialSize; i++) {
CONNECTION_POOL.addLast(SqlConnectImpl.fetchConnection());
}
}
}
/**
* 獲取連接
*
* @param timeOut 超時時間 ,0或小於0則永不超時。
* @return 獲取到的連接,超時則爲空,需要處理
* @throws InterruptedException 拋出的線程狀態異常
*/
public Connection fetchConn(long timeOut) throws InterruptedException {
synchronized (CONNECTION_POOL) {
// 超時時間小於等於0,即爲永不超時
if (timeOut <= 0) {
// 判斷當前線程池是否爲空,爲空則進入等待狀態,等待其他線程來喚醒
while (CONNECTION_POOL.isEmpty()) {
CONNECTION_POOL.wait();
}
// 獲取並返回第一個鏈接
return CONNECTION_POOL.removeFirst();
} else {
// 求出超時的時間以及剩餘的超時時間
long overtime = System.currentTimeMillis() + timeOut;
long remain = timeOut;
// 判斷當前線程池是否爲空並且剩餘超時時間大於0,不滿足的進入等待狀態,等其他線程喚醒,並且求出剩餘超時時間
while (CONNECTION_POOL.isEmpty() && remain > 0) {
CONNECTION_POOL.wait(remain);
remain = overtime - System.currentTimeMillis();
}
// 初始化爲空的連接,
Connection connection = null;
//走到這一步有兩種情況,一種是獲取到了連接池連接,則獲取的連接返回,一種是等待超時,返回空的連接
if (!CONNECTION_POOL.isEmpty()) {
connection = CONNECTION_POOL.removeFirst();
}
//返回連接
return connection;
}
}
}
/**
* 放回數據庫連接
*
* @param conn 連接
*/
public void releaseConn(Connection conn) {
//判斷連接是否爲空
if (conn != null) {
synchronized (CONNECTION_POOL) {
//將連接放入容器尾部
CONNECTION_POOL.addLast(conn);
//喚醒所有等待的線程
CONNECTION_POOL.notifyAll();
}
}
}
}
這就是實現了數據庫連接池的類,在類中有
CONNECTION_POOL 連接池容器
DbPool 構造方法來初始化容器
fetchConn 獲取連接
releaseConn 歸還連接
其中用到的SqlConnectImpl是自定義的一個類,實現了Connection接口,沒有做其他處理
public class SqlConnectImpl implements Connection {
/**
* 拿一個數據庫連接
*
* @return
*/
public static Connection fetchConnection() {
return new SqlConnectImpl();
}
}
從代碼中可以看出我們使用了wait()和notifyAll()方法,這兩個方法是我們實現等待超時模式的關鍵點,wait是讓線程處於等待狀態,要麼是等待時間到了或者被其他線程喚醒,否則會一直等待下去,正是通過這個機制來完成了等待超時模式的連接池。
連接池測試
現在連接池已經實現了,接下來就是要進行對連接池進行測試了。
我們定義了連接池中連接池大小爲10,並定義了CountDownLatch 來控制線程,保證main中的統計最後執行(如果對CountDownLatch 不太瞭解的,可以看看之前的一篇關於CountDownLatch 的博客鏈接: 高併發(8)- 線程併發工具類-CountDownLatch.)
然後有100個線程,每個線程操作40次,也就是獲取4000次連接,然後統計其中獲取到連接的次數和超時的次數,這兩個分別用了原子操作類來記錄,保證線程的安全。
/**
* @version 1.0
* @Description 數據庫線程池測試
* @Author 殘冬十九
* @Date 2020/6/15
*/
public class DbPoolTest {
/**
* 初始化線程池,大小爲10
*/
static DbPool pool = new DbPool(10);
/**
* 控制器:控制main線程將會等待所有Work結束後才能繼續執行
*/
static CountDownLatch end;
public static void main(String[] args) throws InterruptedException {
// 線程數量
int threadCount = 100;
//定義CountDownLatch長度
end = new CountDownLatch(threadCount);
//每個線程的操作次數
int count = 40;
//計數器:統計可以拿到連接的線程,原子操作,保證線程安全
AtomicInteger got = new AtomicInteger();
//計數器:統計沒有拿到連接的線程,原子操作,保證線程安全
AtomicInteger notGot = new AtomicInteger();
//循環創建線程獲取連接
for (int i = 0; i < threadCount; i++) {
new Thread(new Worker(count, got, notGot), "worker_" + i).start();
}
// main線程在此處等待,等待所有線程執行完畢
end.await();
System.out.println("總共嘗試了: " + (threadCount * count));
System.out.println("拿到連接的次數: " + got);
System.out.println("沒能連接的次數: " + notGot);
}
static class Worker implements Runnable {
int count;
AtomicInteger got;
AtomicInteger notGot;
public Worker(int count, AtomicInteger got, AtomicInteger notGot) {
this.count = count;
this.got = got;
this.notGot = notGot;
}
@Override
public void run() {
while (count > 0) {
try {
// 從線程池中獲取連接,如果10ms內無法獲取到,將會返回null
// 分別統計連接獲取的數量got和未獲取到的數量notGot
Connection connection = pool.fetchConn(10);
if (connection != null) {
try {
//獲取到連接則進行操作
connection.createStatement();
connection.commit();
} finally {
//最後釋放連接,並且成功獲取連接數量+1
pool.releaseConn(connection);
got.incrementAndGet();
}
} else {
//沒有獲取到連接則等待超時,未獲取到連接數量+1
notGot.incrementAndGet();
System.out.println(Thread.currentThread().getName()
+ "等待超時!");
}
} catch (Exception ex) {
} finally {
//線程操作數量-1
count--;
}
}
//線程執行完後執行一次CountDownLatch
end.countDown();
}
}
}
從結果圖中看出,我們請求了4000次連接池,其中3975次獲取到了連接,25次連接超時了。
不管spring還是Mybatis中的來連接池都是類似的實現,不過這個是比較簡單的實現,不過原理都是一樣的。
這個就是簡單的等待超時模式的連接池,希望對讀者有幫助。