@[TOC](并發(fā)實(shí)戰(zhàn)(1)- 模擬等待超時(shí)模式的連接池)
前言
我們來進(jìn)行并發(fā)的實(shí)戰(zhàn),用等待超時(shí)模式來實(shí)現(xiàn)連接池的功能松邪。
不管是在Spring還是Mybatis中的的連接池墓猎,都是按照等待超時(shí)的思想來實(shí)現(xiàn)的。
接下來簡單的來實(shí)現(xiàn)一個(gè)等待超時(shí)模式的連接池
什么是等待超時(shí)模式的連接池
什么是等待超時(shí)模式的連接池米诉,從名字中可以看到就是通過等待與等待超時(shí)來實(shí)現(xiàn)一個(gè)連接池币狠。連接池也就是一個(gè)池子容器游两,里面放著我們定義好的連接,在需要的時(shí)候從中取出連接漩绵,并且在使用完成后歸還接連贱案,減少了連接創(chuàng)建與銷毀的性能消耗。
所以我們想實(shí)現(xiàn)一個(gè)等待超時(shí)模式的連接池止吐,需要實(shí)現(xiàn)以下幾步
一宝踪、創(chuàng)建一個(gè)連接池類
二、定義一個(gè)連接池容器
三祟印、初始化連接池容器
四肴沫、定義取出連接方法
五粟害、定義歸還連接方法
連接池的實(shí)現(xiàn)
/**
* @version 1.0
* @Description 數(shù)據(jù)庫連接池
* @Author 殘冬十九
* @Date 2020/6/15
*/
public class DbPool {
/**
* 數(shù)據(jù)庫連接池容器
*/
private final static LinkedList<Connection> CONNECTION_POOL = new LinkedList<>();
/**
* 初始化連接池蕴忆,傳入初始化大小,當(dāng)大于0會(huì)初始化連接池容器
*
* @param initialSize
*/
public DbPool(int initialSize) {
if (initialSize > 0) {
for (int i = 0; i < initialSize; i++) {
CONNECTION_POOL.addLast(SqlConnectImpl.fetchConnection());
}
}
}
/**
* 獲取連接
*
* @param timeOut 超時(shí)時(shí)間 ,0活小于0則永不超時(shí)悲幅。
* @return 獲取到的連接套鹅,超時(shí)則為空站蝠,需要處理
* @throws InterruptedException 拋出的線程狀態(tài)異常
*/
public Connection fetchConn(long timeOut) throws InterruptedException {
synchronized (CONNECTION_POOL) {
// 超時(shí)時(shí)間小于等于0,即為永不超時(shí)
if (timeOut <= 0) {
// 判斷當(dāng)前線程池是否為空卓鹿,為空則進(jìn)入等待狀態(tài)菱魔,等待其他線程來喚醒
while (CONNECTION_POOL.isEmpty()) {
CONNECTION_POOL.wait();
}
// 獲取并返回第一個(gè)鏈接
return CONNECTION_POOL.removeFirst();
} else {
// 求出超時(shí)的時(shí)間以及剩余的超時(shí)時(shí)間
long overtime = System.currentTimeMillis() + timeOut;
long remain = timeOut;
// 判斷當(dāng)前線程池是否為空并且剩余超時(shí)時(shí)間大于0,不滿足的進(jìn)入等待狀態(tài)吟孙,等其他線程喚醒澜倦,并且求出剩余超時(shí)時(shí)間
while (CONNECTION_POOL.isEmpty() && remain > 0) {
CONNECTION_POOL.wait(remain);
remain = overtime - System.currentTimeMillis();
}
// 初始化為空的連接,
Connection connection = null;
//走到這一步有兩種情況杰妓,一種是獲取到了連接池連接藻治,則獲取的連接返回,一種是等待超時(shí)巷挥,返回空的連接
if (!CONNECTION_POOL.isEmpty()) {
connection = CONNECTION_POOL.removeFirst();
}
//返回連接
return connection;
}
}
}
/**
* 放回?cái)?shù)據(jù)庫連接
*
* @param conn 連接
*/
public void releaseConn(Connection conn) {
//判斷連接是否為空
if (conn != null) {
synchronized (CONNECTION_POOL) {
//將連接放入容器尾部
CONNECTION_POOL.addLast(conn);
//喚醒所有等待的線程
CONNECTION_POOL.notifyAll();
}
}
}
}
這就是實(shí)現(xiàn)了數(shù)據(jù)庫連接池的類桩卵,在類中有
CONNECTION_POOL 連接池容器
DbPool 構(gòu)造方法來初始化容器
fetchConn 獲取連接
releaseConn 歸還連接
其中用到的SqlConnectImpl是自定義的一個(gè)類,實(shí)現(xiàn)了Connection接口倍宾,沒有做其他處理
public class SqlConnectImpl implements Connection {
/**
* 拿一個(gè)數(shù)據(jù)庫連接
*
* @return
*/
public static Connection fetchConnection() {
return new SqlConnectImpl();
}
}
從代碼中可以看出我們使用了wait()和notifyAll()方法雏节,這兩個(gè)方法是我們實(shí)現(xiàn)等待超時(shí)模式的關(guān)鍵點(diǎn),wait是讓線程處于等待狀態(tài)高职,要么是等待時(shí)間到了或者被其他線程喚醒钩乍,否則會(huì)一直等待下去,正是通過這個(gè)機(jī)制來完成了等待超時(shí)模式的連接池怔锌。
連接池測試
現(xiàn)在連接池已經(jīng)實(shí)現(xiàn)了件蚕,接下來就是要進(jìn)行對連接池進(jìn)行測試了。
我們定義了連接池中連接池大小為10产禾,并定義了CountDownLatch 來控制線程排作,保證main中的統(tǒng)計(jì)最后執(zhí)行(如果對CountDownLatch 不太了解的,可以看看之前的一篇關(guān)于CountDownLatch 的博客鏈接: 高并發(fā)(8)- 線程并發(fā)工具類-CountDownLatch.)
然后有100個(gè)線程亚情,每個(gè)線程操作40次妄痪,也就是獲取4000次連接,然后統(tǒng)計(jì)其中獲取到連接的次數(shù)和超時(shí)的次數(shù)楞件,這兩個(gè)分別用了原子操作類來記錄衫生,保證線程的安全。
/**
* @version 1.0
* @Description 數(shù)據(jù)庫線程池測試
* @Author 殘冬十九
* @Date 2020/6/15
*/
public class DbPoolTest {
/**
* 初始化線程池土浸,大小為10
*/
static DbPool pool = new DbPool(10);
/**
* 控制器:控制main線程將會(huì)等待所有Work結(jié)束后才能繼續(xù)執(zhí)行
*/
static CountDownLatch end;
public static void main(String[] args) throws InterruptedException {
// 線程數(shù)量
int threadCount = 100;
//定義CountDownLatch長度
end = new CountDownLatch(threadCount);
//每個(gè)線程的操作次數(shù)
int count = 40;
//計(jì)數(shù)器:統(tǒng)計(jì)可以拿到連接的線程罪针,原子操作,保證線程安全
AtomicInteger got = new AtomicInteger();
//計(jì)數(shù)器:統(tǒng)計(jì)沒有拿到連接的線程黄伊,原子操作泪酱,保證線程安全
AtomicInteger notGot = new AtomicInteger();
//循環(huán)創(chuàng)建線程獲取連接
for (int i = 0; i < threadCount; i++) {
new Thread(new Worker(count, got, notGot), "worker_" + i).start();
}
// main線程在此處等待,等待所有線程執(zhí)行完畢
end.await();
System.out.println("總共嘗試了: " + (threadCount * count));
System.out.println("拿到連接的次數(shù): " + got);
System.out.println("沒能連接的次數(shù): " + 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內(nèi)無法獲取到,將會(huì)返回null
// 分別統(tǒng)計(jì)連接獲取的數(shù)量got和未獲取到的數(shù)量notGot
Connection connection = pool.fetchConn(10);
if (connection != null) {
try {
//獲取到連接則進(jìn)行操作
connection.createStatement();
connection.commit();
} finally {
//最后釋放連接墓阀,并且成功獲取連接數(shù)量+1
pool.releaseConn(connection);
got.incrementAndGet();
}
} else {
//沒有獲取到連接則等待超時(shí)毡惜,未獲取到連接數(shù)量+1
notGot.incrementAndGet();
System.out.println(Thread.currentThread().getName()
+ "等待超時(shí)!");
}
} catch (Exception ex) {
} finally {
//線程操作數(shù)量-1
count--;
}
}
//線程執(zhí)行完后執(zhí)行一次CountDownLatch
end.countDown();
}
}
}
從結(jié)果圖中看出,我們請求了4000次連接池斯撮,其中3975次獲取到了連接经伙,25次連接超時(shí)了。
不管spring還是Mybatis中的來連接池都是類似的實(shí)現(xiàn)勿锅,不過這個(gè)是比較簡單的實(shí)現(xiàn)帕膜,不過原理都是一樣的。
這個(gè)就是簡單的等待超時(shí)模式的連接池溢十,希望對讀者有幫助泳叠。