数据库连接池-连接的关闭内幕

我们经常会遇到这样那样的连接未关闭的问题,连接没有及时关闭导致的直接后果就是内存泄漏直至 down 机。我们也都知道解决的方式,但是在解决了问题之后经常会思考为什么会这样呢?连接 close() 掉,然后在创建不是很浪费 cpu 等系统资源嘛?有没有更好的方法解决呢?大家也经常听到连接池、线程池之类的线程、池的概念,那么究竟这些概念与我们的连接有什么关系呢?
下面我就想就上面的问题谈谈我的一点浅见,请大家批评指正。
大家都知道 java 语言是一种语言级的多线程机制的面向对象语言。比如说, java 的基类 object ,它就有一些诸如 wati (), notify (), notifyall ()等线程控制的方法。如果大家学习过操作系统的化,我想应该知道线程存在同步和异步的问题,解决的方法很多,其中就有“信号量机制”实现线程的同步, java 的这种同步机制与操作系统大同小异。同步机制是一个比较复杂的问题,如果感兴趣可以找一本操作系统的书看看。
下面简单介绍一些进程和线程的概念:
1. 进程:
进程是资源分配和独立运行的基本单位。进程的定义很多,下面列举一些
进程是程序的一次执行;
进程是可以和别的计算机并发执行的计算;
进程可定义为一个数据结构及能在其上进行操作的一个程序
进程是一个程序及其数据在处理机上顺序执行时发生的活动;
进程时程序在一个数据集合上的运行过程,时系统进行资源分配和调度的一个独立单位。
2. 线程
由于进程是一个资源拥有者,因而在进程的创建、撤消和切换过程中,系统必须为之付出较大的时空开销。也正因为如此,在系统中所设置的进程数目不宜过多,进程切换的频率也不宜过高,但这也就限制了并发程度的进一步提高。因此便引出了线程的概念
把线程作为调度和分派的基本单位,而把进程作为资源拥有的基本单位,使传统进程的两个属性分开,线程便能轻装运行,从而显著提高系统的并发程度。
在同一个进程内可以有多个线程;
同一个进程内的线程切换不会引起进程切换;
一个进程的线程切换到另一个进程的线程时会引起进程切换
3. JSP/SERVLET
而我们常用的 jsp/ervlet 这种 j2ee 的体系结构正是建立在 java 的多线程机制之上的。 JSP/SERVLET 容器会自动使用线程池等技术来支持系统的运行。因此, JSP/SERVLET 的实质是一种线程技术, JSP 会在运行时被编译成 servlet 来运行,
当客户端向服务器发出一个请求时, servlet 容器会分配一个线程专门处理这个请求,线程内容就是 JSP/servlet 应用程序。
这部分内容与本主体无关,只是顺便说说。


4. 线程池

首先介绍一下线程池:
线程的创建和销毁,以及切换,执行都是要耗费系统资源的。当系统访问量比较大的时候,服务器内就会创建太多的线程,直至资源完全消耗,这对于应用系统的正常运行是有致命伤害的。
为了能够在访问尖峰时限制活动线程的数量,同时减少线程频繁创建和销毁带来的系统开销,提高系统的大访问量的处理性能和速度,需要事先创建一定数量的线程供调用者循环反复使用,这就是“池”技术。
线程的基本原理是基于队列queue这种数据结构的,通过不断查询队列queue是否有可以运行的线程。如果有,就立即运行线程,没有,则锁定等待,直到有新的线程加入被解锁。(这种锁定机制,就是所谓的“信号量机制”)。
一种线程池必须解决如下的问题:死锁、资源不足、并发错误、线程泄漏和请求过载。下面我们具体举一个成熟的开源线程池的例子来说明线程池的原理:
PooledExecutor pool=new PooledExecutor(new BoundedBuffer(20),100);
pool.setMinimumPoolSize(10);//最小线程数为10
poole.setKeepAliveTime(-1);//线程一直运行
上面的语句设置了线程的最大数目为100,这样,就可以保护系统不会因为访问量增加导致线程数目的无限增加。使用该线程池如下:
pool.execute(java.lang.Runnable 自己的线程);
这一句实际上是将“自己的线程”加入一个队列中,而队列(先进先出FIFO)另一段正开启多个线程不断读取这个队列,一旦队列中有空闲的线程,线程管理器就将读取并分配线程来运行它。
public void execute(Runnable command) throws InterruptedException {
for (;;) { //一直循环
synchronized (this) {
if (!shutdown_) { //确保线程池没有关闭
int size = poolSize_; //当前线程池中线程的数目
if (size < minimumPoolSize_) { //如果当前线程数目少于线程池最小数目
addThread(command);
return;
}
//如果目前线程池中有超过或等于最小数目的线程
//分配一个存在的空闲线程来运行command,handOff是队列
if (handOff_.offer(command, 0)) {
return;
}
//如果不能分配已有的线程来运行command,那么创建一个新线程
if (size < maximumPoolSize_) {
addThread(command);
return;
}
}
}
//如果阻塞,则请求帮助
if (getBlockedExecutionHandler().bolckedAction(command)) {
return;
}
}
}
由上面的代码可见,PooledExecutor线程池的原理是,当执行execute加载一个应用系统的线程时,线程池内部首先检查当前线程数目是否达到设定的最小数目。如果没有达到,启动新线程运行;如果达到了,那么检查有无空闲线程可用;如果没有空闲的,则创建新线程,直到达到最大数目。
使用线程池的好处是:首先是循环使用,一经创建后,空闲的线程可以被反复使用,提高了运行效率;其次有最大数目的限制,保证了系统的安全性。
5. 连接池
终于轮到连接池了,通过上面的介绍,我们对线程及线程池都有个一个大致的了解。
在正常情况下,直接使用JDBC调用数据库可以满足一个小型系统的要求,但是当系统规模比较大的情况下,就会出现数据库的访问量迅速提升而令服务器不堪重负的现象,因而为了解决这一性能问题,常常会使用数据库连接池作为一个缓存的方式解决。
连接池类似上面介绍的线程池。
每次数据库连接的建立都需要花费一定的时空费用,而使用连接池,可以事先建立连接。当应用程序需要开始使用时,就从连接池中获取一个连接使用,应用程序使用完毕,通过close()方法将连接归还连接池。讲到这里,我门就不必在担心close()方法会不会影响性能了,完全可以放心大胆的使用。因为,它实际上并没有关闭连接,而是将连接归还连接池,供下次使用。
当并发增加是,连接池会不断的自动创建新的连接满足调用,直到达到连接池的最大数目;当连接池连接减少甚至没有时,连接池自动关闭一些连接,保持最小数目。
因此连接池的使用节省了连接建立时间,消除了数据库频繁连接带来的开销和瓶颈。
小提示:不知道大家有没有注意到配置websphere时有关于连接池最大最小数目的配置。呵呵,道理就在这。
那么,我们经常面对的连接未关闭的问题导致的系统速度很慢的问题就很容易说明了,就是因为线程池已经达到了最大数目,没有可用的了。所以,其他操作只有等待的份,等待那些应用用完了,被垃圾回收了,才能释放出可用的连接。
数据库连接池代码
常用的数据库连接池代码,转载的
总结了一下数据库连接池的用法。
数据库连接是最常用的提高性能的方法,总结了一下:
1 定义数据库访问接口
public interface IDatabaseConnection
{
public abstract Connection createConnection();
public abstract void closeConnection(Connection conn);
}
2 实现该接口
2.1 直接连接,连接速度较慢,用户量较大时资源占用很大
public Connection createConnection()
{
try
{
SystemParameter SP = new SystemParameter();
Class.forName(DBDRIVER).newInstance();
Connection con =
DriverManager.getConnection(
DBDRIVER_URL,
DBUSER,
DBPASSWORD);
return con;
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
return null;
}
catch (SQLException e)
{
e.printStackTrace();
return null;
}
catch (InstantiationException e)
{
e.printStackTrace();
return null;
}
catch (IllegalAccessException e)
{
e.printStackTrace();
return null;
}
}
public void closeConnection(Connection conn)
{
if(conn!=null) try
{
conn.close();
}
catch (SQLException e)
{

e.printStackTrace();
}
}
2.2 DBConnectionManager、DBConnectionPool方式
这个连接池是网上比较多的连接池代码
public class DBConnectionManager
{
static private DBConnectionManager instance; /*唯一实例*/
static private int clients;
/*该计数代表引用DBConnectionManager唯一实例的客户程序总数,
它将被用于控制连接池的关闭操作 */
//Added by leo on 2001-5-23
static private String DEFAULTCONNECTIONPOOL = "homepage";
private Vector drivers = new Vector();
private PrintWriter logPrint;
private Hashtable pools = new Hashtable();
private SystemParameter sp = new SystemParameter();
/**
返回唯一实例。如果是第一次调用此方法,则创建实例
*/
static synchronized public DBConnectionManager getInstance()
{
if (instance == null)
{
instance = new DBConnectionManager();
}
clients++;
return instance;
}
/**
建构函数私有以防止其它对象创建本类实例
*/
public DBConnectionManager()
{
init();
}
/** Added by leo on 2001-5-23
使用默认的连接池名,创建并返回新连接
*
* @return Connection 可用连接或null
*/
public Connection getConnection()
{
return getConnection(DEFAULTCONNECTIONPOOL);
}
/**
获得一个可用的(空闲的)连接.如果没有可用连接,且已有连接数小于最大连接数
限制,则创建并返回新连接
*
* @param name 在属性文件中定义的连接池名字
* @return Connection 可用连接或null
*/
public Connection getConnection(String name)
{
DBConnectionPool pool = (DBConnectionPool) pools.get(name);
if (pool != null)
{
return pool.getConnection();
}
return null;
}
/**
获得一个可用连接.若没有可用连接,且已有连接数小于最大连接数限制,
则创建并返回新连接.否则,在指定的时间内等待其它线程释放连接.
*
* @param name 连接池名字
* @param time 以毫秒计的等待时间
* @return Connection 可用连接或null
*/
public Connection getConnection(String name, long time)
{
DBConnectionPool pool = (DBConnectionPool) pools.get(name);
if (pool != null)
{
return pool.getConnection(time);
}
return null;
}
/** Added by leo on 2001-5-23
将连接对象返回给默认的连接池
*
* @param con 连接对象
*/
public void freeConnection(Connection con)
{
String name = DEFAULTCONNECTIONPOOL;
freeConnection(name, con);
}
/**
将连接对象返回给由名字指定的连接池
*
* @param name 在属性文件中定义的连接池名字
* @param con 连接对象
*/
public void freeConnection(String name, Connection con)
{
DBConnectionPool pool = (DBConnectionPool) pools.get(name);
if (pool != null)
{
pool.freeConnection(con);
}
}
/**
关闭所有连接,撤销驱动程序的注册
*/
public synchronized void release()
{ // 等待直到最后一个客户程序调用
if (--clients != 0)
{
return;
}
Enumeration allPools = pools.elements();
while (allPools.hasMoreElements())
{
DBConnectionPool pool = (DBConnectionPool) allPools.nextElement();
pool.release();
}
Enumeration allDrivers = drivers.elements();
while (allDrivers.hasMoreElements())
{
Driver driver = (Driver) allDrivers.nextElement();
try
{
DriverManager.deregisterDriver(driver);
log("撤消JDBC驱动程序" + driver.getClass().getName() + "的注册");
}
catch (SQLException e)
{
log(e, "无法撤消下列JDBC驱动程序的注册" + driver.getClass().getName());
}
}
}
/**
根据指定属性创建连接池实例.
*
* @param props 连接池属性
*/
private void createPools()
{

String poolName = sp.DBPOOLNAME;
String url = sp.DBDRIVER_URL;
String user = sp.DBUSER;
String password = sp.DBPASSWORD;
int max = sp.DBMAXCONN;

DBConnectionPool pool =
new DBConnectionPool(poolName, url, user, password, max);
pools.put(poolName, pool);
log("成功创建连接池" + poolName);
}
/**
读取属性完成初始化
*/
private void init()
{
logPrint=new PrintWriter(System.out);
loadDrivers();
createPools();
}
/**
装载和注册所有JDBC驱动程序
*
* @param props 属性
*/
private void loadDrivers()
{

String driverClassName = sp.DBDRIVER;
try
{
Driver driver =
(Driver) Class.forName(driverClassName).newInstance();
DriverManager.registerDriver(driver);
drivers.addElement(driver);
log("成功JDBC注册驱动程序" + driverClassName);
}
catch (Exception e)
{
log("无法注册JDBC驱动程序;" + driverClassName + ",错误:" + e);
}

}
/**
将文本信息写入日志文件
缺省为packaged
*/
void log(String msg)
{
logPrint.println(new java.util.Date() + ": " + msg);
}
/**
将文本信息与异常写入日志文件
*/
void log(Throwable e, String msg)
{
logPrint.println(new java.util.Date() + ": " + msg);
e.printStackTrace(logPrint);
}
}
------------------------------------------------------
class DBConnectionPool
{
private int checkedOut;
private PrintWriter logPrint;
private Vector freeConnections = new Vector();
private int maxConn;
private String name;
private String password;
private String URL;
private String user;
/**
创建新的连接池
*@param name 连接池名字
*@param URL 数据库的JDBC URL
*@param user 数据库帐号,或null
*@param password 密码,或null
*@param maxConn 此连接池允许建立的最大连接数
*/
public DBConnectionPool(String name, String URL, String user, String password, int maxConn)
{
this.name = name;
this.URL = URL;
this.user = user;
this.password = password;
this.maxConn = maxConn;

logPrint = new PrintWriter(System.out);

}
/**
将不再使用的连接返回给连接池
*@param con 客户程序释放的连接
*/
public synchronized void freeConnection(Connection con)
{
freeConnections.addElement(con);
checkedOut--;
notifyAll();
}
/**
从连接池获得一个可用连接,如没有空闲的连接且当前连接数小于最大连接数,则创建新连接,
如原来登记为可用的连接不再有效,则从向量删除之,然后递归调用自己以尝试新的可用连接
*/
public synchronized Connection getConnection()
{
Connection con = null;
if(freeConnections.size() > 0)
{// 获取向量中第一个可用连接
con = (Connection)freeConnections.firstElement();
freeConnections.removeElementAt(0);
try
{
if(con.isClosed())
{
log("从连接池" + name + "删除一个无效连接");
con = getConnection();
}
}
catch(SQLException e)
{
log("从连接池" + name + "删除一个无效连接");
con = getConnection();
}
}
else if(maxConn == 0 || checkedOut < maxConn)
{
con = newConnection();
}
if(con != null)
{
checkedOut++;
}
return con;
}
/**
从连接池获取可用连接,可以指定客户程序能够等待的最长时间
*@param timeout 以毫秒计的等待时间限制
*/
public synchronized Connection getConnection(long timeout)
{
long startTime = new Date().getTime();
Connection con;
while((con = getConnection()) == null)
{
try
{
wait(timeout);
}
catch(InterruptedException e){}
if((new Date().getTime() - startTime) >= timeout)
{
return null;
}
}
return con;
}
/**
关闭所有连接
*/
public synchronized void release()
{
Enumeration allConnections = freeConnections.elements();
while(allConnections.hasMoreElements())
{
Connection con = (Connection)allConnections.nextElement();
try
{
con.close();
log("关闭连接池" + name + "中的一个连接");
}
catch(SQLException e)
{
log(e, "无法关闭连接池" + name + "中的连接");
}
}
freeConnections.removeAllElements();
}
/**
创建新的连接
*/
private Connection newConnection()
{
Connection con = null;
try
{
if(user == null)
{
con = DriverManager.getConnection(URL);
}
else
{
con = DriverManager.getConnection(URL, user, password);
}
log("连接池" + name + "创建一个新的连接");
}
catch(SQLException e)
{
log(e, "无法创建下列URL的连接" + URL);
return null;
}
return con;
}
/**
将文本信息写入日志文件
缺省为packaged
*/
void log(String msg)
{
logPrint.println(new Date() + ": " + msg);
}
/**
将文本信息与异常写入日志文件
*/
void log(Throwable e, String msg)
{
logPrint.println(new Date() + ": " + msg);
e.printStackTrace(logPrint);
}
}
---------------------------------
DBConnectionManager connMgr = DBConnectionManager.getInstance();
public Connection createConnection()
{
Connection dBConn = connMgr.getConnection(new SystemParameter().DBPOOLNAME);
return dBConn;
}
public void closeConnection(Connection conn)
{
connMgr.freeConnection(new SystemParameter().DBPOOLNAME, conn);
connMgr.release();
}
经过测试,使用这种方法,会造成数据库大量死锁,证明该连接池实现方法存在隐患
也可能是因为它不属于webserver容器自身的,不具有可管理性。
2.3 tomcat连接池
public Connection createConnection()
{
try
{
Context initCtx = new InitialContext();
DataSource ds;
ds = (DataSource) initCtx.lookup("java:comp/env/"+new SystemParameter().JDBC_SOURCE);
return ds.getConnection();
}
catch (NamingException e)
{
e.printStackTrace();
return null;
}
catch (SQLException e)
{
e.printStackTrace();
return null;
}
}
public void closeConnection(Connection conn)
{
if(conn!=null)try
{
conn.close();
conn=null;
}
catch (SQLException e)
{

e.printStackTrace();
}

}
2.4 weblogic连接池
public Connection createConnection()
{
Context ctx = null;
Hashtable ht = new Hashtable();
ht.put(Context.INITIAL_CONTEXT_FACTORY, CONTEXT_FACTORY);
ht.put(Context.PROVIDER_URL, URL);
// ht.put(Context.SECURITY_PRINCIPAL, "admin");
// ht.put(Context.SECURITY_CREDENTIALS, "11111111");
Connection conn = null;
try
{
ctx = new InitialContext(ht);
DataSource ds = (DataSource) ctx.lookup(POOL_NAME);
return ds.getConnection();
}
catch (NamingException e)
{
e.printStackTrace();
}
catch (SQLException e)
{
e.printStackTrace();
}
return null;
}
public void closeConnection(Connection conn)
{
if (conn != null)
try
{
conn.close();
conn = null;
}
catch (SQLException e)
{
e.printStackTrace();
}
}
3,4的方法比较好