从零开始实现Java多线程数据库连接池(附一个神秘的问题)

Posted 唐大麦

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零开始实现Java多线程数据库连接池(附一个神秘的问题)相关的知识,希望对你有一定的参考价值。

本例采用mysql数据库,因此请先下载mysql-connection.jar

在我们的实际开发中,离不开和数据库打交道。而和数据库的通信,离不开数据库连接。
通常用JDBC连接数据库时,需要加载数据驱动,然后再通过接口返回数据库连接。
一般分为两步:
1、加载驱动至内存
Class.forName(“com.mysql.jdbc.Driver”);

2、创建并获取连接,返回的是JDBC中的Connection
DriverManager.getConnection(url, user, password)

示例:

//要连接的数据库URL
String url = "jdbc:mysql://localhost:3306/tangwmdb";
//连接的数据库时使用的用户名
String username = "root";
//连接的数据库时使用的密码
String password = "root";

//1.加载驱动
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());不推荐使用这种方式来加载驱动
Class.forName("com.mysql.jdbc.Driver");//推荐使用这种方式来加载驱动

//2.获取与数据库的链接
Connection conn = DriverManager.getConnection(url, username, password);

//3.获取用于向数据库发送sql语句的statement
Statement st = conn.createStatement();

String sql = "select id,name,password from members";
//4.向数据库发sql,并获取代表结果集的resultset
ResultSet rs = st.executeQuery(sql);

//5.取出结果集的数据
while(rs.next())
    System.out.println("id=" + rs.getObject("id"));
    System.out.println("name=" + rs.getObject("name"));
    System.out.println("password=" + rs.getObject("password"));


//6.关闭链接,释放资源
rs.close();
st.close();
conn.close();

众所周知,创建数据库连接需要消耗较多的资源,且创建时间也较长。如果网站一天100万PV(假设每个页面都有DB读取或修改操作),程序就需要创建100万次连接,极大的浪费资源。
事实上,同一时间需要创建数据库连接的请求数量并不多,一般几百个足够了。那么我们可以根据需要创建一个连接池,它负责分配、管理和释放数据库连接,它允许应用程序重复使用同一个现有的数据库连接,而不是重新建立一个。这里用到了设计模式中的一个模式:享元模式(Flyweight)
比如我们的连接池中有1000条连接,请求来时,连接池从池中分配一条给请求,用完后收回,而不是销毁,等到下次有请求来时,又可以重复分配使用。

当使用了数据库连接池之后,在项目的实际开发中就不需要编写连接数据库的代码了,直接从数据源获得数据库的连接。比如:

//DBCP 数据库连接池
DataSource ds = BasicDataSourceFactory.createDataSource(prop);
Connection conn = ds.getConnection();

可以看到创建连接的工作很简单,因为复杂的分配、回收功能都交给了连接池去处理。

当前有一些开源的数据连接池实现:

  • DBCP 数据库连接池
  • C3P0 数据库连接池

另外阿里开源项目Druid(整个项目由数据库连接池、插件框架和SQL解析器组成)中的数据库连接池被很多互联网公司都采用在生产环境中。

编写自己的数据库连接池

编写的连接池需要做到以下几个基本点:
1、可配置并管理多个连接节点的连接池

2、始使化时根据配置中的初始连接数创建指定数量的连接
3、在连接池没有达到最大连接数之前,如果有可用的空闲连接就直接使用空闲连接,如果没有,就创建新的连接。
4、当连接池中的活动连接数达到最大连接数,新的请求进入等待状态,直到有连接被释放。
5、由于数据库连接闲置久了会超时关闭,因此需要连接池采用机制保证每次请求的连接都是有效可用的。
6、线程安全
7、连接池内部要保证指定最小连接数量的空闲连接。
对于最小连接数在实际应用中的效果以及与初始连接数的区别,其实理解的不是很透。在程序中我采用的方式是,如果 活动连接数 + 空闲连接数 < 最小连接数,就补齐对应数量(最小连接数 - 活动连接数 - 空闲连接数)的空闲连接

摘录一段:

数据库连接池的最小连接数和最大连接数的设置要考虑到以下几个因素:
最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费。
最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作。
如果最小连接数与最大连接数相差很大,那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接。不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,它将被放到连接池中等待重复使用或是超时后被释放。

系统结构:

1.连接池接口IConnectionPool:里面定义一些基本的获取连接的一些方法。
2.连接池接口实现ConnectionPool
3.连接池管理DBConnectionManager:管理不同的连接池,所有的连接都是通过这里获得。
4.其它工具类,诸如属性读取类PropertiesManager,属性保存类DBPropertyBean。

工程结构:

工程代码:

DBPropertyBean.java

package com.twm.TDBConnectionPool;
public class DBPropertyBean 

    private String nodeName;
    //数据连接驱动
    private String driverName;
    //数据连接url
    private String url;
    //数据连接username
    private String username;
    //数据连接密码
    private String password;
    //连接池最大连接数
    private int maxConnections ;
    //连接池最小连接数
    private int minConnections;
    //连接池初始连接数
    private int initConnections;
    //重连间隔时间 ,单位毫秒
    private int conninterval ;
    //获取连接超时时间 ,单位毫秒,0永不超时
    private int timeout ;

    //构造方法
    public DBPropertyBean()
        super();
    

    //下面是getter and setter

    /**
     * 获取数据库连接节点名称
     * @return
     */
    public String getNodeName() 
        return nodeName;
    

    /**
     * 设置数据库连接节点名称
     * @param nodeName
     */
    public void setNodeName(String nodeName) 
        this.nodeName = nodeName;
    

    /**
     * 获取数据库驱动
     * @return
     */
    public String getDriverName() 
        return driverName;
    

    /**
     * 设置数据库驱动
     * @param driverName
     */
    public void setDriverName(String driverName) 
        this.driverName = driverName;
    

    /**
     * 获取数据库url
     * @return
     */
    public String getUrl() 
        return url;
    

    /**
     * 设置数据库url
     * @param url
     */
    public void setUrl(String url) 
        this.url = url;
    

    /**
     * 获取用户名
     * @return
     */
    public String getUsername() 
        return username;
    

    /**
     * 设置用户名
     * @param username
     */
    public void setUsername(String username) 
        this.username = username;
    

    /**
     * 获取数据库连接密码
     * @return
     */
    public String getPassword()
        return password;
    

    /**
     * 设置数据库连接密码
     * @param password
     */
    public void setPassword(String password) 
        this.password = password;
    

    /**
     * 获取最大连接数
     * @return
     */
    public int getMaxConnections() 
        return maxConnections;
    

    /**
     * 设置最大连接数
     * @param maxConnections
     */
    public void setMaxConnections(int maxConnections) 
        this.maxConnections = maxConnections;
    

    /**
     * 获取最小连接数(也是数据池初始连接数)
     * @return
     */
    public int getMinConnections() 
        return minConnections;
    

    /**
     * 设置最小连接数(也是数据池初始连接数)
     * @param minConnections
     */
    public void setMinConnections(int minConnections) 
        this.minConnections = minConnections;
    

    /**
     * 获取初始加接数
     * @return
     */
    public int getInitConnections() 
        return initConnections;
    

    /**
     * 设置初始连接数
     * @param initConnections
     */
    public void setInitConnections(int initConnections) 
        this.initConnections = initConnections;
    

    /**
     * 获取重连间隔时间,单位毫秒
     * @return
     */
    public int getConninterval() 
        return conninterval;
    

    /**
     * 设置重连间隔时间,单位毫秒
     * @param conninterval
     */
    public void setConninterval(int conninterval) 
        this.conninterval = conninterval;
    

    /**
     * 获取连接超时时间,单位毫秒
     * @return
     */
    public int getTimeout() 
        return timeout;
    

    /**
     * 设置连接超时时间 ,单位毫秒,0-无限重连
     * @param timeout
     */
    public void setTimeout(int timeout) 
        this.timeout = timeout;
    

IConnectionPool.java

package com.twm.TDBConnectionPool;

import java.sql.Connection;
import java.sql.SQLException;

public interface IConnectionPool 
    /**
     * 获取一个数据库连接,如果等待超过超时时间,将返回null
     * @return 数据库连接对象
     */
    public Connection getConnection();

    /**
     * 获得当前线程的连接库连接
     * @return 数据库连接对象
     */
    public Connection getCurrentConnecton();

    /**
     * 释放当前线程数据库连接
     * @param conn 数据库连接对象
     * @throws SQLException
     */
    public void releaseConn(Connection conn) throws SQLException;

    /**
     * 销毁清空当前连接池
     */
    public void destroy();

    /**
     * 连接池可用状态
     * @return 连接池是否可用
     */
    public boolean isActive();

    /**
     * 定时器,检查连接池
     */
    public void checkPool();

    /**
     * 获取线程池活动连接数
     * @return 线程池活动连接数
     */
    public int getActiveNum();

    /**
     * 获取线程池空闲连接数
     * @return 线程池空闲连接数
     */
    public int getFreeNum();

ConnectionPool.java

package com.twm.TDBConnectionPool;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;


/** 
 * 类说明 :友元类,包内可见,不提供给客户程序直接访问。
 */
class ConnectionPool implements IConnectionPool 

    private static final Logger log = Logger.getLogger(ConnectionPool.class);

    private DBPropertyBean propertyBean=null;

    //连接池可用状态
    private Boolean isActive = true;

    // 空闲连接池 。由于List读写频繁,使用LinkedList存储比较合适
    private LinkedList<Connection> freeConnections = new LinkedList<Connection>();  
    // 活动连接池。活动连接数 <= 允许最大连接数(maxConnections)
    private LinkedList<Connection> activeConnections = new LinkedList<Connection>(); 

    //当前线程获得的连接
    private ThreadLocal<Connection> currentConnection= new ThreadLocal<Connection>();

    //构造方法无法返回null,所以取消掉。在下面增加了CreateConnectionPool静态方法。
    private ConnectionPool()
        super();
    

    public static ConnectionPool CreateConnectionPool(DBPropertyBean propertyBean) 
        ConnectionPool connpool=new ConnectionPool();
        connpool.propertyBean = propertyBean;

        //加载驱动 

        //在多节点环境配置下,因为在这里无法判断驱动是否已经加载,可能会造成多次重复加载相同驱动。
        //因此加载驱动的动作,挪到connectionManager管理类中去实现了。
        /*try 
            Class.forName(connpool.propertyBean.getDriverName());
            log.info("加载JDBC驱动"+connpool.propertyBean.getDriverName()+"成功");
         catch (ClassNotFoundException e) 
            log.info("未找到JDBC驱动" + connpool.propertyBean.getDriverName() + ",请引入相关包");
            return null;
        */

        //基本点2、始使化时根据配置中的初始连接数创建指定数量的连接
        for (int i = 0; i < connpool.propertyBean.getInitConnections(); i++) 
            try 
                Connection conn = connpool.NewConnection();
                connpool.freeConnections.add(conn);
             catch (SQLException | ClassNotFoundException e) 
                log.error(connpool.propertyBean.getNodeName()+"节点连接池初始化失败");
                return null;
            
        

        connpool.isActive = true;
        return connpool;
    



    /**
     * 检测连接是否有效
     * @param 数据库连接对象
     * @return Boolean
     */
    private Boolean isValidConnection(Connection conn) throws SQLException
        try 
            if(conn==null || conn.isClosed())
                return false;
            
         catch (SQLException e) 
            throw new SQLException(e);
        
        return true;
    

    /**
     * 创建一个新的连接
     * @return 数据库连接对象
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    private Connection NewConnection() throws ClassNotFoundException,
            SQLException 

        Connection conn = null;
        try 
            if (this.propertyBean != null) 
                //Class.forName(this.propertyBean.getDriverName());
                conn = DriverManager.getConnection(this.propertyBean.getUrl(),
                        this.propertyBean.getUsername(),
                        this.propertyBean.getPassword());
            
         catch (SQLException e) 
            throw new SQLException(e);
        



        return conn;
    


    @Override
    public synchronized Connection getConnection() 
        Connection conn = null;
        if (this.getActiveNum() < this.propertyBean.getMaxConnections()) 
            // 分支1:当前使用的连接没有达到最大连接数  
            // 基本点3、在连接池没有达到最大连接数之前,如果有可用的空闲连接就直接使用空闲连接,如果没有,就创建新的连接。
            if (this.getFreeNum() > 0) 
                // 分支1.1:如果空闲池中有连接,就从空闲池中直接获取
                log.info("分支1.1:如果空闲池中有连接,就从空闲池中直接获取");
                conn = this.freeConnections.pollFirst();

                //连接闲置久了也会超时,因此空闲池中的有效连接会越来越少,需要另一个进程进行扫描监测,不断保持一定数量的可用连接。
                //在下面定义了checkFreepools的TimerTask类,在checkPool()方法中进行调用。

                //基本点5、由于数据库连接闲置久了会超时关闭,因此需要连接池采用机制保证每次请求的连接都是有效可用的。
                try 
                    if(this.isValidConnection(conn))
                        this.activeConnections.add(conn);
                        currentConnection.set(conn);
                    else
                        conn = getConnection();//同步方法是可重入锁
                    
                 catch (SQLException e) 
                    e.printStackTrace();
                
             else 
                // 分支1.2:如果空闲池中无可用连接,就创建新的连接
                log.info("分支1.2:如果空闲池中无可用连接,就创建新的连接");
                try 
                    conn = this.NewConnection();
                    this.activeConnections.add(conn);
                 catch (ClassNotFoundException | SQLException e) 
                    e.printStackTrace();
                
            
         else 
            // 分支2:当前已到达最大连接数  
            // 基本点4、当连接池中的活动连接数达到最大连接数,新的请求进入等待状态,直到有连接被释放。
            log.info("分支2:当前已到达最大连接数 ");
            long startTime = System.currentTimeMillis();

            //进入等待状态。等待被notify(),notifyALL()唤醒或者超时自动苏醒  
            try
                this.wait(this.propertyBean.getConninterval());  
            catch(InterruptedException e)   
                log.error("线程等待被打断");  
            

            //若线程超时前被唤醒并成功获取连接,就不会走到return null。
            //若线程超时前没有获取连接,则返回null。
            //如果timeout设置为0,就无限重连。
            if(this.propertyBean.getTimeout()!=0)
                if(System.currentTimeMillis() - startTime > this.propertyBean.getTimeout())  
                    return null;  
            
            conn = this.getConnection();

        
        return conn;
    


    @Override
    public Connection getCurrentConnecton() 
        Connection conn=currentConnection.get();
        try 
            if(! isValidConnection(conn))
                conn=this.getConnection();
            
         catch (SQLException e) 
            e.printStackTrace();
        
        return conn;
    


    @Override
    public synchronized void releaseConn(Connection conn) throws SQLException 

        log.info(Thread.currentThread().getName()+"关闭连接:activeConnections.remove:"+conn);
        this.activeConnections.remove(conn);
        this.currentConnection.remove();
        //活动连接池删除的连接,相应的加到空闲连接池中
        try 
            if(isValidConnection(conn))
                freeConnections.add(conn);
            else
                freeConnections.add(this.NewConnection());
            

         catch (ClassNotFoundException | SQLException e) 
            e.printStackTrace();
        
        //唤醒getConnection()中等待的线程
        this.notifyAll();
    

    @Override
    public synchronized void destroy() 
        for (Connection conn : this.freeConnections)   
            try 
                if (this.isValidConnection(conn))  
                    conn.close();
                
             catch (SQLException e) 
                e.printStackTrace();
               
          
        for (Connection conn : this.activeConnections)   
            try 
                if (this.isValidConnection(conn))  
                    conn.close();
                
             catch (SQLException e) 
                e.printStackTrace();
             
        
        this.isActive = false;
        this.freeConnections.clear();
        this.activeConnections.clear();
    

    @Override
    public boolean isActive() 
        return this.isActive;
    


    @Override
    public void checkPool() 

        final String nodename=this.propertyBean.getNodeName();

        ScheduledExecutorService ses=Executors.newScheduledThreadPool(2);

        //功能一:开启一个定时器线程输出状态
        ses.scheduleAtFixedRate(new TimerTask() 
            @Override
            public void run() 
                System.out.println(nodename +"空闲连接数:"+getFreeNum());  
                System.out.println(nodename +"活动连接数:"+getActiveNum());   

            
        , 1, 1, TimeUnit.SECONDS);

        //功能二:开启一个定时器线程,监测并维持空闲池中的最小连接数
        ses.scheduleAtFixedRate(new checkFreepools(this), 1, 5, TimeUnit.SECONDS);
    

    @Override
    public synchronized int getActiveNum() 
        return this.activeConnections.size();
    

    @Override
    public synchronized int getFreeNum() 
        return this.freeConnections.size();
    

    //基本点6、连接池内部要保证指定最小连接数量的空闲连接
    class checkFreepools extends TimerTask 
        private ConnectionPool conpool = null;

        public checkFreepools(ConnectionPool cp) 
            this.conpool = cp;
        

        @Override
        public void run() 
            if (this.conpool != null && this.conpool.isActive()) 
                int poolstotalnum = conpool.getFreeNum()
                        + conpool.getActiveNum();
                int subnum = conpool.propertyBean.getMinConnections()
                        - poolstotalnum;

                if (subnum > 0) 
                    System.out.println(conpool.propertyBean.getNodeName()
                            + "扫描并维持空闲池中的最小连接数,需补充" + subnum + "个连接");
                    for (int i = 0; i < subnum; i++) 
                        try 
                            conpool.freeConnections
                                    .add(conpool.NewConnection());
                         catch (ClassNotFoundException | SQLException e) 
                            e.printStackTrace();
                        
                    

                
            

        

    



ConnectionManager.java

package com.twm.TDBConnectionPool;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.log4j.Logger;
import com.twm.TDBConnectionPool.util.PropertiesManager;


public class ConnectionManager 
    private final Logger log = Logger.getLogger(ConnectionPool.class);

    private static ConnectionManager dbm = null;

    /** 
     * 加载的驱动器名称集合 
     */  
    private Set<String> drivers = new HashSet<String>(); 

    /**
     * 数据库连接池字典
     * 为每个节点创建一个连接池(可配置多个节点)
     */
    private ConcurrentHashMap<String, IConnectionPool> pools = new ConcurrentHashMap<String, IConnectionPool>();



    private ConnectionManager() 
        createPools();
    


    /**
     * 装载JDBC驱动程序,并创建连接池
     */
    private void createPools() 
        String str_nodenames = PropertiesManager.getProperty("nodename");
        //基本点1、可配置并管理多个连接节点的连接池
        for (String str_nodename : str_nodenames.split(",")) 
            DBPropertyBean dbProperty = new DBPropertyBean();
            dbProperty.setNodeName(str_nodename);

            //验证url配置正确性
            String url = PropertiesManager.getProperty(str_nodename + ".url");
            if (url == null) 
                log.error(str_nodename+"节点的连接字符串为空,请检查配置文件");
                continue;
            
            dbProperty.setUrl(url);

            //验证driver配置正确性
            String driver = PropertiesManager.getProperty(str_nodename + ".driver");
            if (driver == null) 
                log.error(str_nodename+"节点的driver驱动为空,请检查配置文件");
                continue;
            
            dbProperty.setDriverName(driver);


            //验证user配置正确性
            String user = PropertiesManager.getProperty(str_nodename + ".user");
            if (user == null) 
                log.error(str_nodename+"节点的用户名设置为空,请检查配置文件");
                continue;
            
            dbProperty.setUsername(user);


            //验证password配置正确性
            String password = PropertiesManager.getProperty(str_nodename + ".password");
            if (password == null) 
                log.error(str_nodename+"节点的密码设置为空,请检查配置文件");
                continue;
            
            dbProperty.setPassword(password);

            //验证最小连接数配置正确性
            String str_minconnections=PropertiesManager.getProperty(str_nodename + ".minconnections");
            int minConn;
            try 
                minConn = Integer.parseInt(str_minconnections);
             catch (NumberFormatException e) 
                log.error(str_nodename + "节点最小连接数设置错误,默认设为5");
                minConn=5;
            
            dbProperty.setMinConnections(minConn);

            //验证初始连接数配置正确性
            String str_initconnections=PropertiesManager.getProperty(str_nodename + ".initconnections");
            int initConn;
            try 
                initConn = Integer.parseInt(str_initconnections);
             catch (NumberFormatException e) 
                log.error(str_nodename + "节点初始连接数设置错误,默认设为5");
                initConn=5;
            
            dbProperty.setInitConnections(initConn);

            //验证最大连接数配置正确性
            String str_maxconnections=PropertiesManager.getProperty(str_nodename + ".maxconnections");
            int maxConn;
            try 
                maxConn = Integer.parseInt(str_maxconnections);
             catch (NumberFormatException e) 
                log.error(str_nodename + "节点最大连接数设置错误,默认设为20");
                maxConn=20;
            
            dbProperty.setMaxConnections(maxConn);

            //验证conninterval配置正确性
            String str_conninterval=PropertiesManager.getProperty(str_nodename + ".conninterval");
            int conninterval;
            try 
                conninterval = Integer.parseInt(str_conninterval);
             catch (NumberFormatException e) 
                log.error(str_nodename + "节点重新连接间隔时间设置错误,默认设为500ms");
                conninterval = 500;
            
            dbProperty.setConninterval(conninterval);

            //验证timeout配置正确性
            String str_timeout=PropertiesManager.getProperty(str_nodename + ".timeout");
            int timeout;
            try 
                timeout = Integer.parseInt(str_timeout);
             catch (NumberFormatException e) 
                log.error(str_nodename + "节点连接超时时间设置错误,默认设为2000ms");
                timeout = 2000;
            
            dbProperty.setTimeout(timeout);

            //创建驱动
            if(!drivers.contains(dbProperty.getDriverName()))
                try 
                    Class.forName(dbProperty.getDriverName());
                    log.info("加载JDBC驱动"+dbProperty.getDriverName()+"成功");
                    drivers.add(dbProperty.getDriverName());
                 catch (ClassNotFoundException e) 
                    log.error("未找到JDBC驱动" + dbProperty.getDriverName() + ",请引入相关包");
                    e.printStackTrace();
                
            

            //创建连接池。这里采用同步方法实现的连接池类ConnectionPool。
            //(如果后面我们还有别的实现方式,只需要更改这里就行了。)
            IConnectionPool cp = ConnectionPool.CreateConnectionPool(dbProperty);
            if (cp != null) 
                pools.put(str_nodename, cp);
                cp.checkPool();
                log.info("创建" + str_nodename + "数据库连接池成功");
             else 
                log.info("创建" + str_nodename + "数据库连接池失败");
            
        

    

    /**
     * 获得单例
     * 
     * @return DBConnectionManager单例
     */
    public synchronized static ConnectionManager getInstance() 
        if (dbm == null) 
            dbm = new ConnectionManager();
        
        return dbm;
    

    /**
     * 从指定连接池中获取可用连接
     * 
     * @param poolName要获取连接的连接池名称
     * @return连接池中的一个可用连接或null
     */
    public Connection getConnection(String poolName) 
        IConnectionPool pool =  pools.get(poolName);
        return pool.getConnection();
    


    /**
     * 回收指定连接池的连接
     * 
     * @param poolName连接池名称
     * @param conn要回收的连接
     */
    public void closeConnection(String poolName, Connection conn) throws SQLException 
        IConnectionPool pool = pools.get(poolName);
        if (pool != null) 
            try 
                pool.releaseConn(conn);
             catch (SQLException e) 
                log.error("回收"+poolName+"池中的连接失败。");
                throw new SQLException(e);
            
        else
            log.error("找不到"+poolName+"连接池,无法回收");
        
    

    /**
     * 关闭所有连接,撤销驱动器的注册
     */
    public void destroy() 
        for (Map.Entry<String, IConnectionPool> poolEntry : pools.entrySet()) 
            IConnectionPool pool = poolEntry.getValue();
            pool.destroy();
        
        log.info("已经关闭所有连接");
    

PropertiesManager.java

package com.twm.TDBConnectionPool.util;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;


public class PropertiesManager 
    private static Properties pro = new Properties();

    private PropertiesManager() 

    

    static 
        try 
            pro.load(PropertiesManager.class.getClassLoader().getResourceAsStream("DB.properties"));
         catch (IOException e) 
            e.printStackTrace();
        
    

    public static String getProperty(String key) 
        return pro.getProperty(key);
    

    public static String getProperty(String key, String defaultValue) 
        return pro.getProperty(key, defaultValue);
    

    public static Enumeration<?> propertiesNames() 
        return pro.propertyNames();
    

testpool.java

package com.twm.TDBConnectionPool.run;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;

import com.twm.TDBConnectionPool.ConnectionManager;
import com.twm.TDBConnectionPool.DBPropertyBean;
import com.twm.TDBConnectionPool.IConnectionPool;
import com.twm.TDBConnectionPool.util.PropertiesManager;


public class testpool 

    /**
     * @param args
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws InterruptedException 

        List<Thread> threadlist=new ArrayList<Thread>();
        for(int i=1;i<=3;i++)
            Thread subThread = new Thread(new workrun(i));
            subThread.start();
            threadlist.add(subThread);
        
        for (Iterator<Thread> iterator = threadlist.iterator(); iterator.hasNext();) 
            Thread thread = iterator.next();
            thread.join();
        
        //ConnectionManager.getInstance().destroy();

    


class workrun implements Runnable
    int i;
    public workrun(int i)
        this.i=i;
    
    @Override
    public void run() 
        ConnectionManager cm = ConnectionManager.getInstance();

         //1.从数据池中获取数据库连接
        Connection conn = cm.getConnection("default");
        System.out.println("线程 " + Thread.currentThread().getName() + "获得连接:" + conn);

        //模拟查询耗时操作
        try 
            Thread.sleep(1000);
         catch (InterruptedException e) 
            e.printStackTrace();
        


         //2.获取用于向数据库发送sql语句的statement
        Statement st = null;
        try 
            st = conn.createStatement();
         catch (SQLException e) 
            e.printStackTrace();
        

        String sql = "select * from product where id="+i;
        //3.向数据库发sql,并获取代表结果集的resultset
        //4.取出结果集的数据
        ResultSet rs = null;
        try 
            rs = st.executeQuery(sql);
            while(rs.next())
                System.out.println("productname=" + rs.getObject("productname"));
                //System.out.println("price=" + rs.getObject("price"));
                //System.out.println("cateid=" + rs.getObject("cateid"));
            
         catch (SQLException e) 
            e.printStackTrace();
        

        //模拟查询耗时操作
        try 
            Thread.sleep(1000);
         catch (InterruptedException e) 
            e.printStackTrace();
        

        //5.关闭链接,释放资源
        try 
            rs.close();
            st.close();
            //conn.close();
            cm.closeConnection("default",conn);
         catch (SQLException e) 
            e.printStackTrace();
        

    

DB.properties

nodename=default,testdb

default.driver=com.mysql.jdbc.Driver
default.url=jdbc:mysql://localhost:3306/tangwenmingdb
default.user=root
default.password=root
default.maxconnections=10
default.minconnections=5
default.initconnections=2
default.conninterval=500
default.timeout = 5000

testdb.driver=com.mysql.jdbc.Driver
testdb.url=jdbc:mysql://192.168.1.17:3306/test
testdb.user=root
testdb.password=root
testdb.maxconnections=10
testdb.minconnections=2
testdb.initconnections=从零开始手写 mybatis jdbc pool 从零实现数据库连接池

从零开始学Java-数据库连接池的选择 Druid

线程池的简单调用(附动态图)

从零手写一个JAVA连接池组件,窥视架构团队的开发日常

从零开始学Java-Day17

多线程(线程池原理)