首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫

高併發場景下的 Java MySQL 連接池實現與壓測驗證

     編輯:MySQL 資料庫相關知識

針對 App 服務端高併發訪問 MySQL 的場景,本文提供一個輕量級、線程安全的連接池實現,採用雙重檢查鎖單例模式、HashMap 管理連接狀態、阻塞等待擴容策略,經 1 萬併發壓測 5 秒完成,驗證可用性。代碼結構清晰,適合二次開發或教學參考。

核心設計思路

  • 單例模式:volatile + 雙重檢查鎖,確保多線程環境下唯一實例。
  • 連接狀態管理:Map<Connection, Boolean>,true=空閒,false=使用中。
  • 動態擴縮容:初始化 initPoolSize,上限 maxPoolSize,耗盡時 wait() 退避重試。
  • 生命週期維護:獲取時檢測 isClosed(),歸還時重置 autoCommit=true,異常連接自動剔除。
  • 資源安全釋放:finally 塊確保 ResultSet/Statement/Connection 正確關閉/歸池。

完整實現代碼

連接池核心類 MySQLPool

package com.junones.test;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

public class MySQLPool {
    private static volatile MySQLPool pool;
    private MysqlDataSource ds;
    private Map<Connection, Boolean> map;
    private String url = "jdbc:mysql://localhost:3306/test";
    private String username = "root";
    private String password = "root1234";
    private int initPoolSize = 10;
    private int maxPoolSize = 200;
    private int waitTime = 100; // ms

    private MySQLPool() { init(); }

    public static MySQLPool getInstance() {
        if (pool == null) {
            synchronized (MySQLPool.class) {
                if (pool == null) pool = new MySQLPool();
            }
        }
        return pool;
    }

    private void init() {
        try {
            ds = new MysqlDataSource();
            ds.setUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
            ds.setCacheCallableStmts(true);
            ds.setConnectTimeout(1000);
            ds.setLoginTimeout(2000);
            ds.setUseUnicode(true);
            ds.setEncoding("UTF-8");
            ds.setZeroDateTimeBehavior("convertToNull");
            ds.setMaxReconnects(5);
            ds.setAutoReconnect(true);
            map = new HashMap<Connection, Boolean>();
            for (int i = 0; i < initPoolSize; i++) {
                map.put(getNewConnection(), true);
            }
        } catch (Exception e) { e.printStackTrace(); }
    }

    public Connection getNewConnection() {
        try { return ds.getConnection(); }
        catch (SQLException e) { e.printStackTrace(); }
        return null;
    }

    public synchronized Connection getConnection() {
        Connection conn = null;
        try {
            for (Entry<Connection, Boolean> entry : map.entrySet()) {
                if (entry.getValue()) {
                    conn = entry.getKey();
                    map.put(conn, false);
                    break;
                }
            }
            if (conn == null) {
                if (map.size() < maxPoolSize) {
                    conn = getNewConnection();
                    map.put(conn, false);
                } else {
                    wait(waitTime);
                    conn = getConnection(); // 遞歸重試
                }
            }
        } catch (Exception e) { e.printStackTrace(); }
        return conn;
    }

    public void releaseConnection(Connection conn) {
        if (conn == null) return;
        try {
            if (map.containsKey(conn)) {
                if (conn.isClosed()) map.remove(conn);
                else {
                    if (!conn.getAutoCommit()) conn.setAutoCommit(true);
                    map.put(conn, true);
                }
            } else { conn.close(); }
        } catch (SQLException e) { e.printStackTrace(); }
    }
}

壓測驗證類 TestMySQLPool

package com.junones.test;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class TestMySQLPool {
    private static volatile int a;
    private synchronized static void incr() { a++; }

    public static void main(String[] args) throws InterruptedException {
        int times = 10000;
        long start = System.currentTimeMillis();
        for (int i = 0; i < times; i++) {
            new Thread(() -> {
                MySQLPool pool = MySQLPool.getInstance();
                Connection conn = pool.getConnection();
                Statement stmt = null;
                ResultSet rs = null;
                try {
                    stmt = conn.createStatement();
                    rs = stmt.executeQuery("select id, name from t_test");
                    while (rs.next()) {
                        System.out.println(rs.getInt(1) + ", " + rs.getString(2));
                    }
                } catch (SQLException e) { e.printStackTrace(); }
                finally {
                    incr();
                    try { if (rs != null) rs.close(); } catch (SQLException ignore) {}
                    try { if (stmt != null) stmt.close(); } catch (SQLException ignore) {}
                    pool.releaseConnection(conn);
                }
            }).start();
        }
        while (true) {
            if (a == times) {
                System.out.println("finished, time:" + (System.currentTimeMillis() - start));
                break;
            }
            Thread.sleep(100);
        }
    }
}

壓測結果與優化建議

測試環境:1 萬併發線程,單表簡單查詢,5 秒完成,吞吐約 2000 QPS。

已知局限與生產級替代方向:

  1. HashMap 非線程安全遍歷,高併發下建議改用 ConcurrentLinkedQueue<PooledConnection> + AtomicInteger 計數器。
  2. wait()/notify() 低級同步原語,易發生虛假喚醒,推薦 ReentrantLock + Condition 或信號量 Semaphore。
  3. 缺乏連接驗證機制(validationQuery)、空閒回收、洩漏檢測、JMX 監控指標。
  4. 生產環境請直接使用 HikariCP(Spring Boot 2.x 默認)、Druid(阿里開源,監控強)、Tomcat JDBC Pool,經過大規模驗證,性能與穩健性遠超手寫版本。

結語

手寫連接池是理解資源池化原理的最佳途徑,但生產系統請站在巨人肩膀上。本文代碼僅供教學與輕量級項目參考,核心邏輯可移植至其他語言實現。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved