併發實戰(1)- 模擬等待超時模式的連接池

併發實戰(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中的來連接池都是類似的實現,不過這個是比較簡單的實現,不過原理都是一樣的。
這個就是簡單的等待超時模式的連接池,希望對讀者有幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章