实现高效的数据库连接池(附带完整代码C#和Java实现)(转)
相关技术:
连接池
引用记数
多线程
C#.Net
Java
适宜人群
数据库应用程序程序员
系统分析员
模块设计师
有一定功底的程序员
目录
引言
数据库连接池(Connection Pool)的工作原理
连接池关键问题分析
并发问题
事务处理
连接池的分配与释放
连接池的配置与维护
关键议题
引用记数
如何实现事务处理
管理连接池
结合代码说明
构造方法
启动服务StartService
停止服务StopService
申请 GetConnectionFormPool
释放DisposeConnection
如何更新属性
如何确定连接是否失效
使用线程管理连接池
threadCreate
threadCheck
其他
--------------------------------------------------------------------------------
引言
一般的数据库应用程序大致都遵循下面的步骤:
初始化程序
用户在UI上输入操作
由用户操作产生数据库操作
将数据库操作递交到数据库服务器
.... (重复2~4)
关闭应用程序
而本文则着重讲解上面第4步骤.在着一步骤中我们经常是,打开数据库连接操作数据库,最后关闭数据库.
在服务器端程序设计上与数据库的操作显得十分重要,因为你要处理的数据操作十分巨大.如果频繁创建数据库连接频繁关闭数据库连接则会引起效率低下甚至引发程序崩溃.
也许我们可以有另一种操作数据库的形式,我们可以在程序运行时打开一个数据库连接,让这个连接永久存在直到程序'死亡',那么这样做也有不安全隐患,我们知道一个对象存在时间越长或被使用次数越多则它表现的越不稳定,着不稳定因素是由于对象内部可能存在的潜在设计问题产生,对于数据库连接对象道理也一样.我们不能保证一个Connection对象里面能一点问题不存在.所以我们也不敢长时间将它长时间占用内存.
既然有这么多的问题由此我们需要一个能帮我们维护数据库连接的东西-它就是连接池,网上有很多的连接池例子,但是多数都是简单的例子,或者介绍比较复杂的连接池原理,没有一个比较完整介绍和实现连接池的例子.这里就介绍你如何自己制作一个连接池.
对于共享资源,有一个很著名的设计模式:资源池(Resource Pool)。该模式正是为了解决资源的频繁分配﹑释放所造成的问题。为解决我们的问题,可以采用数据库连接池技术。数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量﹑使用情况,为系统开发﹑测试及性能调整提供依据。连接池的基本工作原理见下图。
数据库连接池(Connection Pool)的工作原理
连接池关键问题分析
1、并发问题
为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为各个语言自身提供了对并发管理的支持像java,c#等等,使用synchronized(java)lock(C#)关键字即可确保线程是同步的。使用方法可以参考,相关文献。
2、事务处理
我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-ALL-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
我们知道当2个线程公用一个连接Connection对象,而且各自都有自己的事务要处理时候,对于连接池是一个很头疼的问题,因为即使Connection类提供了相应的事务支持,可是我们仍然不能确定那个数据库操作是对应那个事务的,这是由于我们有2个线程都在进行事务操作而引起的。为此我们可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务管理的复杂性。
3、连接池的分配与释放
连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
对于连接的管理可使用一个List。即把已经创建的连接都放入List中去统一管理。每当用户请求一个连接时,系统检查这个List中有没有可以分配的连接。如果有就把那个最合适的连接分配给他(如何能找到最合适的连接文章将在关键议题中指出);如果没有就抛出一个异常给用户,List中连接是否可以被分配由一个线程来专门管理捎后我会介绍这个线程的具体实现。
4、连接池的配置与维护
连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minConnection)和最大连接数(maxConnection)等参数来控制连接池中的连接。比方说,最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过软件需求上得到。
如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。
关键议题
引用记数
在分配、释放策略对于有效复用连接非常重要,我们采用的方法也是采用了一个很有名的设计模式:Reference Counting(引用记数)。该模式在复用资源方面用的非常广泛,我们把该方法运用到对于连接的分配释放上。每一个数据库连接,保留一个引用记数,用来记录该连接的使用者的个数。具体的实现上,我们对Connection类进行进一步包装来实现引用记数。被包装的Connection类我们提供2个方法来实现引用记数的操作,一个是Repeat(被分配出去)Remove(被释放回来);然后利用RepeatNow属性来确定当前被引用多少,具体是哪个用户引用了该连接将在连接池中登记;最后提供IsRepeat属性来确定该连接是否可以使用引用记数技术。一旦一个连接被分配出去,那么就会对该连接的申请者进行登记,并且增加引用记数,当被释放回来时候就删除他已经登记的信息,同时减少一次引用记数。
这样做有一个很大的好处,使得我们可以高效的使用连接,因为一旦所有连接都被分配出去,我们就可以根据相应的策略从使用池中挑选出一个已经正在使用的连接用来复用,而不是随意拿出一个连接去复用。策略可以根据需要去选择,我们有4策略可是使用:
1.ConnLevel_ReadOnly 独占方式
使用空闲的实际连接分配连接资源,并且在该资源释放回之前,该资源在连接池中将不能将其引用分配给其他申请者。如果连接池中所有实际连接资源都已经分配出去,那么即使连接池可以在分配引用资源在该模式下连接迟将不会分配连接资源,连接池会产生一个异常,标志连接池资源耗尽。
例:假如一个实际连接可以被分配5次,那么使用该模式申请连接的话您将损失4个可分配的连接,只将得到一个连接资源。 直至该资源被释放回连接池,连接池才继续分配它剩余的4次机会。
当你在使用连接时可能应用到事务时,可以使用该模式的连接,以确定在事务进行期间您可以对该连接具有独享权限,以避免各个数据库操作访问的干扰。
2.ConnLevel_High 优先级-高
使用空闲的实际连接分配连接资源,并且在该资源释放回之前,该资源在连接池中将可能将其引用分配给其他申请者。*注意:此级别不保证在分配该资源后,仍然保持独立占有连接资源,若想独立占有资源请使用ReadOnely, 因为当连接池达到某一时机时该资源将被重复分配(引用记数)然而这个时机是不可预测的。如果您申请的连接会用于事务处理您可以使用ConnLevel_ReadOnly级别。
3.ConnLevel_None 优先级-中
适当应用引用记数技术分配连接资源。
在该模式下,连接池内部会按照实际连接已经使用次数排序(多->少),然后在结果中选取 1/3 位置的连接资源返回。与优先级-高相同该模式也不具备保持独立占有连接资源的特性。如果您申请的连接会用于事务处理您可以使用ConnLevel_ReadOnly级别。
4.ConnLevel_Bottom 优先级-底
尽可能使用引用记数技术分配连接。在该模式下,连接池内部会按照实际连接已经使用次数排序(多->少),然后在结果中选取被使用最多的返回。该模式适合处理较为不重要的连接资源请求。与优先级-高相同该模式也不具备保持独立占有连接资源的特性。如果您申请的连接会用于事务处理您可以使用ConnLevel_ReadOnly级别。
以上4条策略选自datebasepool_SDK(datebasepool是本文所开发的最终产物)
如何实现事务处理
前面谈到的都是关于使用数据库连接进行普通的数据库访问。对于事务处理,情况就变得比较复杂。因为事务本身要求原子性的保证,此时就要求对于数据库的操作符合"All-All-Nothing"原则,即要么全部完成,要么什么都不做。如果简单的采用上述的连接复用的策略,就会发生问题,因为没有办法控制属于同一个事务的多个数据库操作方法的动作,可能这些数据库操作是在多个连接上进行的,并且这些连接可能被其他非事务方法复用。
Connection本身具有提供了对于事务的支持,具体实现方法请参看Connection的msdn,显式的调用commit或者rollback方法来实现。但是要安全、高效的进行Connection进行复用,就必须提供相应的事务支持机制。我们采用的方法是:用户以ConnLevel_ReadOnly模式申请一个连接之后该连接就由该申请者独自享有该连接,具体事务操作由用户自行设计编写,连接池只提供用户独自占有。
管理连接池
在上文中我们说过这个连接池内部连接管理使用的是独立的线程来工作(threadCreate和threadCheck)threadCreate线程负责创建连接 ,threadCheck线程负责检查每个连接是否达到自己的寿命,标志连接寿命的条件是被引用的次数超过它最大被引用次数,或者达到最大生存时间。这些参数都由ConnStruct类管理,ConnStruct类是包装连接的类,下面定义的就是连接池使用到的属性变量(C#代码)
//属性
private int _realFormPool;//连接池中存在的实际连接数(包含失效的连接)
private int _potentRealFormPool;//连接池中存在的实际连接数(有效的实际连接)
private int _spareRealFormPool;//空闲的实际连接
private int _useRealFormPool;//已分配的实际连接
private int _readOnlyFormPool;//连接池已经分配多少只读连接
private int _useFormPool;//已经分配出去的连接数
private int _spareFormPool;//目前可以提供的连接数
private int _maxConnection;//最大连接数,最大可以创建的连接数目
private int _minConnection;//最小连接数
private int _seepConnection;//每次创建连接的连接数
private int _keepRealConnection;//保留的实际空闲连接,以攻可能出现的ReadOnly使用,当空闲连接不足该数值时,连接池将创建seepConnection个连接
private int _exist = 20;//每个连接生存期限 20分钟
private int _maxRepeatDegree = 5;//可以被重复使用次数(引用记数),当连接被重复分配该值所表示的次数时,该连接将不能被分配出去
//当连接池的连接被分配尽时,连接池会在已经分配出去的连接中,重复分配连接(引用记数)。来缓解连接池压力
private DateTime _startTime;//服务启动时间
private string _connString = null;//连接字符串
private ConnTypeEnum _connType;//连接池连接类型
private PoolState _ps;//连接池状态
//内部对象
private ArrayList al_All = new ArrayList();//实际连接
private Hashtable hs_UseConn = new Hashtable();//正在使用的连接
private System.Timers.Timer time;//监视器记时器
private Thread threadCreate;//创建线程
private bool isThreadCheckRun = false;
Java版本
//属性
private int _RealFormPool; //连接池中存在的实际连接数(包含失效的连接)
private int _PotentRealFormPool; //连接池中存在的实际连接数(有效的实际连接)
private int _SpareRealFormPool; //空闲的实际连接
private int _UseRealFormPool; //已分配的实际连接
private int _ReadOnlyFormPool; //连接池已经分配多少只读连接
private int _UseFormPool; //已经分配出去的连接数
private int _SpareFormPool; //目前可以提供的连接数
private int _MaxConnection; //最大连接数,最大可以创建的连接数目
private int _MinConnection; //最小连接数
private int _SeepConnection; //每次创建连接的连接数
private int _KeepRealConnection; //保留的实际空闲连接,以攻可能出现的ReadOnly使用,当空闲连接不足该数值时,连接池将创建seepConnection个连接
private int _Exist = 20; //每个连接生存期限 20分钟
private String _userID="";
private String _password="";
//可以被重复使用次数(引用记数),当连接被重复分配该值所表示的次数时,该连接将不能被分配出去
//当连接池的连接被分配尽时,连接池会在已经分配出去的连接中,重复分配连接(引用记数)。来缓解连接池压力
private int _MaxRepeatDegree = 5;
private Date _StartTime; //服务启动时间
private String _ConnString = null; //连接字符串
private String _DriveString = null; //驱动字符串
private int _PoolState; //连接池状态
//内部对象
private ArrayList al_All = new ArrayList(); //实际连接
private Hashtable hs_UseConn = new Hashtable(); //正在使用的连接
private CreateThreadProcess threadCreate;
private CheckThreadProcess threadCheck;
当用户调用连接池的StartService方法时,在StartService方法中会通知threadCreate线程创建静态连接,然后将这些静态连接加入到List,同时启动threadCheck线程,threadCheck线程负责检测List中的最小空闲连接是否少于连接池配置的最少空闲连接数,当条件为真时threadCheck线程会负责再次唤醒threadCreate线程同时给threadCreate线程传递这次要创建的连接个数。
对于threadCreate线程有2种工作模式,模式0为初始化创建模式,该模式会创建连接迟池配置的最小连接数目;模式1即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接时的工作模式。
申请连接,当用户申请连接时必须指定一个发起者和一个申请优先级别,优先级由ConnLevel_*系列指定。一旦用户从连接池中申请到一个连接时就将申请到的连接引用和申请者,同时加入到HashTable来注册到连接池。
释放连接,将注册的用户删除。
--------------------------------------------------------------------------------
下面是代码说明
共有Java和C#2个版本,2版本可能捎有不同因为各个语言有自己的特点
Java版本的构造方法
public ConnectionPool() ...{
InitConnectionPool("", "", 200, 30, 10, 5);
}
public ConnectionPool(String connectionString, String driveString) ...{
InitConnectionPool(connectionString, driveString, 200, 30, 10, 5);
}
public ConnectionPool(String connectionString, String driveString, int maxConnection, int minConnection) ...{
InitConnectionPool(connectionString, driveString, maxConnection, minConnection, 10, 5);
}
public ConnectionPool(String connectionString, String driveString, int maxConnection, int minConnection, int seepConnection, int keepRealConnection) ...{
InitConnectionPool(connectionString, driveString, maxConnection, minConnection, seepConnection, keepRealConnection);
}
C#版本的构造方法
public ConnectionPool(string connectionString)
...{ InitConnectionPool(connectionString, ConnTypeEnum.Odbc, 200, 30, 10, 5, 5); }
public ConnectionPool(string connectionString, ConnTypeEnum cte)
...{ InitConnectionPool(connectionString, cte, 200, 30, 10, 5, 5); }
public ConnectionPool(string connectionString, ConnTypeEnum cte, int maxConnection, int minConnection)
...{ InitConnectionPool(connectionString, cte, maxConnection, minConnection, 10, 5, 5); }
public ConnectionPool(string connectionString, ConnTypeEnum cte, int maxConnection, int minConnection, int seepConnection, int keepConnection)
...{ InitConnectionPool(connectionString, cte, maxConnection, minConnection, seepConnection, keepConnection, 5); }
public ConnectionPool(string connectionString, ConnTypeEnum cte, int maxConnection, int minConnection, int seepConnection, int keepConnection, int keepRealConnection)
...{ InitConnectionPool(connectionString, cte, maxConnection, minConnection, seepConnection, keepConnection, keepRealConnection); }
上面2个版本的构造方法都调用InitConnectionPool方法,下面是InitConnectionPool方法实现(C#)
/**//// <summary>
/// 初始化函数
/// </summary>
protected void InitConnectionPool(string connectionString, ConnTypeEnum cte, int maxConnection, int minConnection, int seepConnection, int keepConnection, int keepRealConnection)
...{
if (cte == ConnTypeEnum.None)
throw new ConnTypeExecption();//参数不能是None
_ps = PoolState.UnInitialize;
this._connString = connectionString;
this._connType = cte;
this._minConnection = minConnection;
this._seepConnection = seepConnection;
this._keepRealConnection = keepRealConnection;
this._maxConnection = maxConnection;
this.time = new System.Timers.Timer(500);
this.time.Stop();
this.time.Elapsed += new System.Timers.ElapsedEventHandler(time_Elapsed);
this.threadCreate = new Thread(new ThreadStart(createThreadProcess));
}
在InitConnectionPool方法中我们定义了连接池要连接的数据库类型ConnTypeEnum cte,和连接字符串string connectionString等类型,在方法最后注册了Timer类的Elapsed 事件,在C#版中Timer类就充当threadCheck线程,而Elapsed事件就是线程threadCheck的执行方法体。
由于刚刚初始化连接池对象故threadCheck线程不需要运行所以在InitConnectionPool方法中将Timer关闭。下面是java实现:
private void InitConnectionPool(String connectionString, String driveString, int maxConnection, int minConnection, int seepConnection, int keepRealConnection) ...{
this._PoolState = PoolState_UnInitialize;
this._ConnString = connectionString;
this._DriveString = driveString;
this._MinConnection = minConnection;
this._SeepConnection = seepConnection;
this._KeepRealConnection = keepRealConnection;
this._MaxConnection = maxConnection;
this.threadCheck = new CheckThreadProcess();
this.threadCheck.setDaemon(true);
this.threadCreate = new CreateThreadProcess();
this.threadCreate.setDaemon(true);
this.threadCheck.start();
while(threadCheck.getState()!=Thread.State.WAITING)...{}
}
由于Java中的Timer类需要AWT 的GUI线程组建支持,总不能在我们的连接池中开辟一个Form出来把,因此在Java版本中使用线程来实现Timer,在Java版本中threadCheck一开始就被启动,然后又等待threadCheck线程自己被阻塞又是为什么呢,这样做是为了实现C#版本中stop方法.(由于个人原因这个连接池最初是由C#.Net2.0开发而成的故而在Java中可能存在许多设计不合理)。
由上面代码可以看出一旦连接池被创建出来以后就已经运行了一个threadCheck线程(对于Java版),而这个线程将立刻进入WAITING等待状态。
--------------------------------------------------------------------------------
启动服务StartService
/**//// <summary>
/// 启动服务,线程安全
/// </summary>
public void StartServices()
...{
lock (this)
...{
createThreadMode = 0;//工作模式0
createThreadProcessRun = true;
createThreadProcessTemp = _minConnection;
if (_ps == PoolState.UnInitialize)
threadCreate.Start();
else if (_ps == PoolState.Stop)
threadCreate.Interrupt();
else
throw new PoolNotStopException();//服务已经运行或者未完全结束
time.Start();
}
}
createThreadMode 是设置threadCreate的工作模式为0创建模式,前面我们说过threadCreate有2种工作模式,如果忘记了就回头去看看把。
createThreadProcessTemp是threadCreate工作需要的参数变量threadCreate会在0或者1这两种工作模式下用到这个临时变量该变量作用就是告诉threadCreate要创建多少个连接,下面的一组if .... else 是为了将threadCreate线程运行起来 _ps是表示当前连接池状态,下面是_ps的定义,在Java版本中PoolState只是一组public static final int故在这里不在将源码放出。
在StartServices最后我们启动了time也就是threadCheck线程。
// 连接池状态
public enum PoolState
...{
UnInitialize,// 刚刚创建的对象,表示该对象未被调用过StartSeivice方法。
Initialize,// 初始化中,该状态下服务正在按照参数初始化连接池。
Run,//运行中
Stop//停止状态
}
PoolState定义
Java版本
/** *//**
* 启动连接池服务
* @throws PoolNotStopException 服务已经运行或者未完全结束
*/
public void StartServices() throws PoolNotStopException ...{
synchronized (this) ...{
threadCreate.createThreadMode = 0; //工作模式0
threadCreate.createThreadProcessRun = true;
threadCreate.createThreadProcessTemp = _MinConnection;
if (_PoolState == PoolState_UnInitialize)
threadCreate.start();
else if (_PoolState == PoolState_Stop)
threadCreate.interrupt();
else
throw new PoolNotStopException(); //服务已经运行或者未完全结束
threadCheck.StartTimer();
threadCheck.interrupt();//开始运行
}
}
停止服务StopService
/**//// <summary>
/// 停止服务,线程安全
/// </summary>
public void StopServices()
...{ StopServices(false); }
/**//// <summary>
/// 停止服务,线程安全
/// <param name="needs">是否必须退出;如果指定为false与StartServices()功能相同,如果指定为true。将未收回的连接资源关闭,这将是危险的。认为可能你的程序正在使用此资源。</param>
/// </summary>
public void StopServices(bool needs)
...{
lock (this)
...{
if (_ps == PoolState.Run)
...{
lock (hs_UseConn)
...{
if (needs == true)//必须退出
hs_UseConn.Clear();
else
if (hs_UseConn.Count != 0)
throw new ResCallBackException();//连接池资源未全部回收
}
time.Stop();
while (isThreadCheckRun) ...{ }//等待timer事件结束
createThreadProcessRun = false;
while (threadCreate.ThreadState != ThreadState.WaitSleepJoin) ...{ }//等待可能存在的创建线程结束
lock (al_All)
...{
for (int i = 0; i < al_All.Count; i++)
((ConnStruct)al_All[i]).Dispose();
al_All.Clear();
}
_ps = PoolState.Stop;
}
else
throw new PoolNotRunException();//服务未启动
}
UpdateAttribute();//更新属性
}
在这里我们使用了2层锁第一层锁我们是为了保证同一时间只能有一个线程调用这个方法,而lock (hs_UseConn)则是为了保护hs_UseConn的唯一访问hs_UseConn存放的就是申请连接时注册信息,needs是将未收回的连接资源也关闭,这将是危险的。认为可能你的程序正在使用此资源。
if (hs_UseConn.Count != 0)
throw new ResCallBackException();//连接池资源未全部回收。
这段代码是表示如果用户试图在连接没有全部释放回来时候将会引发异常,同时执行该方法的必要条件是连接池必须处在PoolState_Run状态。
time.Stop();是停止检测线程的运行,while (isThreadCheckRun) ...{ }那为什么要使用这个空循环等待timer的结束呢?原因是Timer类的运行基理,在C#下Timer触发的事件是不同于VB的,在.Net里无论Timer事件处理过程的执行时间是否超过它触发时间间隔在Timer里总会在指定时间被调用,也就是说如果触发间隔却是100毫秒,然而我们的处理过程用了500毫秒来运行代码。这样的话下一个事件的触发将在我们这个事件还没有结束时又进行了,如此一来起不是灾难的!
对于接下来我们还要等待可能存在的threadCreate线程结束,不过在等待它结束时先给他发送一个暂停信号,这个信号由createThreadProcessRun 变量传递进去。也许有人回问为什么不使用join来阻止threadCreate的运行,原因是这样的,假如threadCreate打算创建minConnection个连接,然而恰恰在创建到minConnection - n 个时候我停止也连接池,如果在这里使用join的话我们将不确定下次恢复运行时候究竟要创建多少个连接,最好的解决办法就是叫线程自己找一个安全的地方把自己join。
在下面的代码就是把每一个连接分别销毁掉并且在连接池中将它删除掉。有人说,那你只是删除了你内部的引用,的确我们只删除了内部的引用,因为销毁只是形式的销毁在.net上我们不能控制内存去销毁一个对象,只能叫GC帮忙来清理它。在程序段的最后我们使用私有的UpdateAttribute方法来更新属性字段。
下面是Java版本的代码
/** *//**
* 停止服务,线程安全
* @throws ResCallBackException 资源未全部回首
* @throws PoolNotRunException 服务没有运行
*/
public void StopServices() throws ResCallBackException, PoolNotRunException ...{
StopServices(false);
}
/** *//**
* 停止服务,线程安全
* @param needs boolean 是否必须退出;如果指定为false与StartServices()功能相同,如果指定为true。将未收回的连接资源关闭,这将是危险的。认为可能你的程序正在使用此资源。
* @throws ResCallBackException 服务未启动
* @throws PoolNotRunException 服务没有运行
*/
public void StopServices(boolean needs) throws PoolNotRunException, ResCallBackException ...{
synchronized (this) ...{
if (_PoolState == PoolState_Run) ...{
synchronized (hs_UseConn) ...{
if (needs == true) //必须退出
hs_UseConn.clear();
else
if (hs_UseConn.size() != 0)
throw new ResCallBackException(); //连接池资源未全部回收
}
threadCheck.StopTimer();
while (threadCreate.getState()!=Thread.State.WAITING) ...{ }//等待threadCreate事件结束
threadCreate.createThreadProcessRun = false;
while (threadCreate.getState() != Thread.State.WAITING) ...{} //等待可能存在的创建线程结束
synchronized (al_All) ...{
for (int i = 0; i < al_All.size(); i++)
((ConnStruct) al_All.get(i)).Dispose();
al_All.clear();
}
_PoolState = PoolState_Stop;
} else
throw new PoolNotRunException(); //服务未启动
}
UpdateAttribute(); //更新属性
}
申请 GetConnectionFormPool
/**//// <summary>
/// 在连接池中申请一个连接,使用None级别,线程安全
/// </summary>
/// <param name="gui">发起者</param>
/// <returns>返回申请到的连接</returns>
public DbConnection GetConnectionFormPool(object key)
...{ return GetConnectionFormPool(key, ConnLevel.None); }
/**//// <summary>
/// 在连接池中申请一个连接,线程安全
/// </summary>
/// <param name="key">申请者</param>
/// <param name="cl">申请的连接级别</param>
/// <returns>返回申请到的连接</returns>
public DbConnection GetConnectionFormPool(object key, ConnLevel cl)
...{
lock (this)
...{
if (_ps != PoolState.Run)
throw new StateException();//服务状态错误
if (hs_UseConn.Count == MaxConnectionFormPool)
throw new PoolFullException();//连接池已经饱和,不能提供连接
if (hs_UseConn.ContainsKey(key))
throw new KeyExecption();//一个key对象只能申请一个连接
if (cl == ConnLevel.ReadOnly)
return GetConnectionFormPool_ReadOnly(key);//ReadOnly级别
else if (cl == ConnLevel.High)
return GetConnectionFormPool_High(key);//High级别
else if (cl == ConnLevel.None)
return GetConnectionFormPool_None(key);//None级别
else
return GetConnectionFormPool_Bottom(key);//Bottom级别
}
}
在向连接池申请一个连接资源时我们必须指定一个申请者,同时可以指定一个级别参数最后会根据不同的级别调用不同的策略,这些策略是在上面介绍过的4钟策略,下面不做过多的介绍。
(Java版本不在放出原因代码与C#完全相同)
下面将逐一放出4种策略的完整代码不做过多解释,其功能以在前文介绍过
/**//// <summary>
/// 申请一个连接资源,只读方式,线程安全
/// </summary>
/// <param name="key">申请者</param>
/// <returns>申请到的连接对象</returns>
protected DbConnection GetConnectionFormPool_ReadOnly(object key)
...{
ConnStruct cs = null;
for (int i = 0; i < al_All.Count; i++)
...{
cs = (ConnStruct)al_All[i];
if (cs.Enable == false || cs.Allot == false || cs.UseDegree == _maxRepeatDegree || cs.IsUse == true)
continue;
return GetConnectionFormPool_Return(key, cs, ConnLevel.ReadOnly); //返回得到的连接
}
return GetConnectionFormPool_Return(key, null, ConnLevel.ReadOnly);
}
/**//// <summary>
/// 申请一个连接资源,优先级-高,线程安全
/// </summary>
/// <param name="key">申请者</param>
/// <returns>申请到的连接对象</returns>
protected DbConnection GetConnectionFormPool_High(object key)
...{
ConnStruct cs = null;
ConnStruct csTemp = null;
for (int i = 0; i < al_All.Count; i++)
...{
csTemp = (ConnStruct)al_All[i];
if (csTemp.Enable == false || csTemp.Allot == false || csTemp.UseDegree == _maxRepeatDegree)//不可以分配跳出本次循环。
...{
csTemp = null;
continue;
}
if (csTemp.UseDegree == 0)//得到最合适的
...{
cs = csTemp;
break;
}
else//不是最合适的放置到最佳选择中
...{
if (cs != null)
...{
if (csTemp.UseDegree < cs.UseDegree)
//与上一个最佳选择选出一个最佳的放置到cs中
cs = csTemp;
}
else
cs = csTemp;
}
}
return GetConnectionFormPool_Return(key, cs, ConnLevel.High);//返回最合适的连接
}
/**//// <summary>
/// 申请一个连接资源,优先级-中,线程安全
/// </summary>
/// <param name="key">申请者</param>
/// <returns>申请到的连接对象</returns>
protected DbConnection GetConnectionFormPool_None(object key)
...{
ArrayList al = new ArrayList();
ConnStruct cs = null;
for (int i = 0; i < al_All.Count; i++)
...{
cs = (ConnStruct)al_All[i];
if (cs.Enable == false || cs.Allot == false || cs.UseDegree == _maxRepeatDegree)//不可以分配跳出本次循环。
continue;
if (cs.Allot == true)
al.Add(cs);
}
if (al.Count == 0)
return GetConnectionFormPool_Return(key, null, ConnLevel.None);//发出异常
else
return GetConnectionFormPool_Return(key, ((ConnStruct)al[al.Count / 2]), ConnLevel.None);//返回连接
}
/**//// <summary>
/// 申请一个连接资源,优先级-低,线程安全
/// </summary>
/// <param name="key">申请者</param>
/// <returns>申请到的连接对象</returns>
protected DbConnection GetConnectionFormPool_Bottom(object key)
...{
ConnStruct cs = null;
ConnStruct csTemp = null;
for (int i = 0; i < al_All.Count; i++)
...{
csTemp = (ConnStruct)al_All[i];
if (csTemp.Enable == false || csTemp.Allot == false || csTemp.UseDegree == _maxRepeatDegree)//不可以分配跳出本次循环。
...{
csTemp = null;
continue;
}
else//不是最合适的放置到最佳选择中
...{
if (cs != null)
...{
if (csTemp.UseDegree > cs.UseDegree)
//与上一个最佳选择选出一个最佳的放置到cs中
cs = csTemp;
}
else
cs = csTemp;
}
}
return GetConnectionFormPool_Return(key, cs, ConnLevel.Bottom);//返回最合适的连接
}
Java版本不在放出原因代码与C#完全相同
在上面代码中所有策略都调用GetConnectionFormPool_Return方法。具体是什么呢?让我们来看看
/**//// <summary>
/// 返回DbConnection对象,同时做获得连接时的必要操作
/// </summary>
/// <param name="key">key</param>
/// <param name="cs">ConnStruct对象</param>
/// <param name="cl">级别</param>
/// <param name="readOnly">是否为只读属性</param>
/// <returns></returns>
private DbConnection GetConnectionFormPool_Return(object key, ConnStruct cs, ConnLevel cl)
...{
try
...{
if (cs == null)
throw new Exception();
cs.Repeat();
hs_UseConn.Add(key, cs);
if (cl == ConnLevel.ReadOnly)
...{
cs.Allot = false;
cs.IsRepeat = false;
}
}
catch (Exception e)
...{
throw new OccasionExecption();//连接资源耗尽,或错误的访问时机。
}
finally
...{
UpdateAttribute();//更新属性
}
return cs.Connection;
}
该方法就是返回DbConnection对象,同时将获得的连接引用记数+1,在程序的第一行进行了一下内部错误判断如果是一个空的cs则抛出一个异常,该异常由方法体内接收并且将异常统一包装为OccasionExecption类型异常。hs_UseConn.Add(key, cs);是将被申请到的连接资源注册到连接池。同时也确定这个被申请走的连接是不是以只读方式申请的,如果是则将这个连接锁定。
cs.Allot = false;//设置该连接不能再次被分配
cs.IsRepeat = false;//设置连接不能使用引用记数技术
(Java版本不在放出原因代码与C#完全相同)
--------------------------------------------------------------------------------
释放DisposeConnection
/**//// <summary>
/// 释放申请的数据库连接对象,线程安全
/// <param name="key">key表示数据库连接申请者</param>
/// </summary>
public void DisposeConnection(object key)
...{
lock (hs_UseConn)
...{
ConnStruct cs = null;
if (_ps == PoolState.Run)
...{
if (!hs_UseConn.ContainsKey(key))
throw new NotKeyExecption();//无法释放,不存在的key
cs = (ConnStruct)hs_UseConn[key];
cs.IsRepeat = true;
if (cs.Allot == false)
if (cs.Enable == true)
cs.Allot = true;
cs.Remove();
hs_UseConn.Remove(key);
}
else
throw new PoolNotRunException();//服务未启动
}
UpdateAttribute();//更新属性
}
在释放资源中可以说是上面申请资源的反方向操作其中不同点在于
if (cs.Enable == true)
cs.Allot = true;
这段话的意思是如果连接对象已经失效则不更改会不会再次被分配的属性如果仍然有效则更改为会再次被分配,因为被包装后的Connection不会自己知道自己是否已经超过引用记数所要求的最大上限。
(Java版本不在放出原因代码与C#完全相同)
如何更新属性
/**//// <summary>
/// 更新属性
/// </summary>
private void UpdateAttribute()
...{
int temp_readOnlyFormPool = 0;//连接池已经分配多少只读连接
int temp_potentRealFormPool = 0;//连接池中存在的实际连接数(有效的实际连接)
int temp_spareRealFormPool = 0;//空闲的实际连接
int temp_useRealFormPool = 0;//已分配的实际连接
int temp_spareFormPool = MaxConnectionFormPool;//目前可以提供的连接数
//---------------------------------
lock (hs_UseConn)
...{
_useFormPool = hs_UseConn.Count;
}
//---------------------------------
ConnStruct cs = null;
int n = 0;
lock (al_All)
...{
_realFormPool = al_All.Count;
for (int i = 0; i < al_All.Count; i++)
...{
cs = (ConnStruct)al_All[i];
//只读
if (cs.Allot == false && cs.IsUse == true && cs.IsRepeat == false)
temp_readOnlyFormPool++;
//有效的实际连接
if (cs.Enable == true)
temp_potentRealFormPool++;
//空闲的实际连接
if (cs.Enable == true && cs.IsUse == false)
temp_spareRealFormPool++;
//已分配的实际连接
if (cs.IsUse == true)
temp_useRealFormPool++;
//目前可以提供的连接数
if (cs.Allot == true)
temp_spareFormPool = temp_spareFormPool - cs.RepeatNow;
else
temp_spareFormPool = temp_spareFormPool - _maxRepeatDegree;
}
}
_readOnlyFormPool = temp_readOnlyFormPool;
_potentRealFormPool = temp_potentRealFormPool;
_spareRealFormPool = temp_spareRealFormPool;
_useRealFormPool = temp_useRealFormPool;
_spareFormPool = temp_spareFormPool;
}
在更新属性的方法里我们把每一个属性都做了一个对应的临时变量,为的就是防止在属性更新的时候被用户程序访问到属性更新期间的不准确信息,并且在连接池中所有属性更新操作都在此进行。
--------------------------------------------------------------------------------
如何确定连接是否失效
C#版本
/**//// <summary>
/// 测试ConnStruct是否过期
/// </summary>
/// <param name="cs">被测试的ConnStruct</param>
private void TestConnStruct(ConnStruct cs)
...{
//此次被分配出去的连接是否在此次之后失效
if (cs.UseDegree == _maxRepeatDegree)
cs.SetConnectionLost();//超过使用次数
if (cs.CreateTime.AddMinutes(_exist).Ticks <= DateTime.Now.Ticks)
cs.SetConnectionLost();//连接超时
}
Java版本
/** *//**
* 测试ConnStruct是否过期
* @param cs ConnStruct 被测试的ConnStruct
*/
private void TestConnStruct(ConnStruct cs) ...{
//此次被分配出去的连接是否在此次之后失效
if (cs.GetUseDegree() == _MaxRepeatDegree)
cs.SetConnectionLost(); //超过使用次数
Calendar c = Calendar.getInstance();
c.setTime(cs.GetCreateTime());
c.set(Calendar.MINUTE, c.get(Calendar.MINUTE) + _Exist);
if (c.getTime().after(new Date()))
cs.SetConnectionLost(); //连接超时
}
使用线程管理连接池
threadCreate
前面已经很多次的提到threadCreate和threadCheck。那它们2究竟怎么运行的呢?还是先让我们看一看threadCreate。
/**//// <summary>
/// 创建线程
/// </summary>
private void createThreadProcess()
...{
bool join = false;
int createThreadProcessTemp_inside = createThreadProcessTemp;
_ps = PoolState.Initialize;
while (true)
...{
join = false;
_ps = PoolState.Run;
if (createThreadProcessRun == false)
...{//遇到终止命令
try ...{ threadCreate.Join(); }
catch (Exception e) ...{ }
}
else
...{
if (createThreadMode == 0)
...{
//------------------------begin mode 创建模式
...............在下面中说明
//------------------------end mode
}
else if (createThreadMode == 1)
...{
//------------------------begin mode 增加模式
...............在下面中说明
//------------------------end mode
}
else
join = true;
//-------------------------------------------------------------------------
if (join == true)
...{
UpdateAttribute();//更新属性
try
...{
createThreadProcessTemp = 0;
threadCreate.Join();
}
catch (Exception e)
...{ createThreadProcessTemp_inside = createThreadProcessTemp; }//得到传入的变量
}
}
}
}
在线程中每次执行循环前都要判断自己是不是被要求终止,如果自己被要求终止则无条件将自己立即阻塞上,注意线程仅仅被阻塞没有被终止。
在下面一组createThreadMode 运行模式判断中如果出现不属于0或者1的模式则放出自己被阻止的信息。
在最后线程即将被阻止时将执行更新属性操作并且将自己的运行参数设置为0,因为当运行参数为0时即使线程被唤醒,它也将不会创建任何连接,最终自己还是被阻止。
模式0模式1
threadCheck
ConnStruct cs = null;
time.Stop();//关闭自己
isThreadCheckRun = true;
//如果正在执行创建连接则退出
if (threadCreate.ThreadState != ThreadState.WaitSleepJoin)
return;
//------------------------------------------------------
lock (al_All)
...{
int n = 0;
for (int i = 0; i < al_All.Count; i++)
...{
cs = (ConnStruct)al_All[i];
TestConnStruct(cs);//测试
if (cs.Enable == false && cs.RepeatNow == 0)//没有引用的失效连接
...{
cs.Close();//关闭它
al_All.Remove(cs);//删除
}
}
}
//------------------------------------------------------
UpdateAttribute();//更新属性
if (_spareRealFormPool < _keepRealConnection)//保留空闲实际连接数不足
createThreadProcessTemp = GetNumOf(_realFormPool, _seepConnection, _maxConnection);
else
createThreadProcessTemp = 0;
if (createThreadProcessTemp != 0)
...{
//启动创建线程,工作模式1
createThreadMode = 1;
threadCreate.Interrupt();
}
isThreadCheckRun = false;
time.Start();//打开自己
在C#中由于Timer拥有自己的特性,所以每次触发事件的时候都将自己关闭,以保证同时只有一个事件处理过程在运行,当处理结束时在将自己打开。
threadCheck线程的最主要目的就是测试当前所有实际连接是否有满足失效的连接,如果有失效的连接就将其设置为失效,同时清理已经没有任何引用的失效连接。在做完这些操作之后它还要得到是否要创建补充的连接如果需要创建补充的连接则在将这个数值传递给threadCreate并将之启动。
其中可能出现的问题就是,如果threadCreate已经启动我们在传递参数给它那可能会引发threadCreate的处理异常,因此在threadCheck过程中会首先判断threadCreate是否在阻止中如果不在阻止状态就return等待下次threadCheck的事件。
GetNumOf是为了得到应该创建的个数,代码实现如下
/**//// <summary>
/// 得到当前要增加的量
/// </summary>
private int GetNumOf(int nowNum, int seepNum, int maxNum)
...{
if (maxNum >= nowNum + seepNum)
return seepNum;
else
return maxNum - nowNum;
}
下面是threadCheck的java版,因为前文说了在Java下没有Timer类有的Timer类还仅仅在AWT下工作,因此我们要先做一个Timer类,具体如下
/** *//**
* <p>检测事件</p>
*/
private class CheckThreadProcess extends Thread ...{
private long _Interval = 100; //执行间隔
private boolean timeSize=false;
/** *//**
* 启动记时器
*/
public void StartTimer()
...{
timeSize=true;
if (this.getState()==Thread.State.NEW)
this.start();
else if (this.getState()==Thread.State.WAITING)
this.interrupt();
}
/** *//**
* 停止记时器
*/
public void StopTimer()
...{
timeSize=false;
while(this.getState()!=Thread.State.WAITING)...{ }
}
public void aRun()
...{
}
public void run() ...{
while (true) ...{
try ...{
this.join(_Interval);
if (timeSize == true)
aRun();
else
this.join();
} catch (InterruptedException ex1) ...{ }
}
}
/** *//**
* 设置执行时间间隔
* @param value double 时间间隔
*/
public void setInterval(long value) ...{
_Interval = value;
}
/** *//**
* 获得执行时间间隔
* @return double 时间间隔
*/
public long getInterval() ...{
return _Interval;
}
}
其中aRun方法就是threadCheck在C#下的处理过程代码如下
public void aRun()
...{
ConnStruct cs = null;
//如果正在执行创建连接则退出
if (threadCreate.getState() != Thread.State.WAITING)
return;
//------------------------------------------------------
synchronized (al_All)
...{
for (int i = 0; i < al_All.size(); i++)
...{
cs = (ConnStruct)al_All.get(i);
TestConnStruct(cs);//测试
if (cs.GetEnable() == false && cs.GetRepeatNow() == 0)//没有引用的失效连接
...{
cs.Close();//关闭它
al_All.remove(cs);//删除
}
}
}
//------------------------------------------------------
UpdateAttribute();//更新属性
if (_SpareRealFormPool < _KeepRealConnection)//保留空闲实际连接数不足
threadCreate.createThreadProcessTemp = GetNumOf(_RealFormPool, _SeepConnection, _MaxConnection);
else
threadCreate.createThreadProcessTemp = 0;
//if (threadCreate.createThreadProcessTemp != 0)
//{
// System.out.println("创建" + threadCreate.createThreadProcessTemp);
// System.out.println(threadCreate.getState()+ " this " + this.getState());
//}
if (threadCreate.createThreadProcessTemp != 0)
...{
//启动创建线程,工作模式1
threadCreate.createThreadMode = 1;
threadCreate.interrupt();
}
}
至此一个拥有引用记数,集合线程管理的数据库连接池就完成了。下面是连接池的中被包装的连接代码
其他
/**//// <summary>
/// 连接池中的一个连接类型
/// </summary>
public class ConnStruct : IDisposable
...{
/**//// <summary>
/// 连接池中的连接
/// </summary>
/// <param name="dbc">数据库连接</param>
/// <param name="cte">连接类型</param>
public ConnStruct(DbConnection dbc, ConnTypeEnum cte)
...{
createTime = DateTime.Now;
connect = dbc;
connType = cte;
}
/**//// <summary>
/// 连接池中的连接
/// </summary>
/// <param name="dt">连接创建时间</param>
/// <param name="dbc">数据库连接</param>
/// <param name="cte">连接类型</param>
public ConnStruct(DbConnection dbc, ConnTypeEnum cte, DateTime dt)
...{
createTime = dt;
connect = dbc;
connType = cte;
}
//--------------------------------------------------------------------
private bool enable = true;//是否失效
private bool use = false;//是否正在被使用中
private bool allot = true;//表示该连接是否可以被分配
private DateTime createTime = DateTime.Now;//创建时间
private int useDegree = 0;//被使用次数
private int repeatNow = 0;//当前连接被重复引用多少
private bool isRepeat = true;//连接是否可以被重复引用,当被分配出去的连接可能使用事务时,该属性被标识为true
private ConnTypeEnum connType = ConnTypeEnum.None;//连接类型
private DbConnection connect = null;//连接对象
private object obj = null;//连接附带的信息
属性部分#region 属性部分
/**//// <summary>
/// 表示该连接是否可以被分配
/// </summary>
public bool Allot
...{
get ...{ return allot; }
set ...{ allot = value; }
}
/**//// <summary>
/// 是否失效;false表示失效,只读
/// </summary>
public bool Enable
...{ get ...{ return enable; } }
/**//// <summary>
/// 是否正在被使用中,只读
/// </summary>
public bool IsUse
...{ get ...{ return use; } }
/**//// <summary>
/// 创建时间,只读
/// </summary>
public DateTime CreateTime
...{ get ...{ return createTime; } }
/**//// <summary>
/// 被使用次数,只读
/// </summary>
public int UseDegree
...{ get ...{ return useDegree; } }
/**//// <summary>
/// 当前连接被重复引用多少,只读
/// </summary>
public int RepeatNow
...{ get ...{ return repeatNow; } }
/**//// <summary>
/// 得到数据库连接状态,只读
/// </summary>
public ConnectionState State
...{ get ...{ return connect.State; } }
/**//// <summary>
/// 得到该连接,只读
/// </summary>
public DbConnection Connection
...{ get ...{ return connect; } }
/**//// <summary>
/// 连接是否可以被重复引用
/// </summary>
public bool IsRepeat
...{
get ...{ return isRepeat; }
set ...{ isRepeat = value; }
}
/**//// <summary>
/// 连接类型,只读
/// </summary>
public ConnTypeEnum ConnType
...{ get ...{ return connType; } }
/**//// <summary>
/// 连接附带的信息
/// </summary>
public object Obj
...{
get ...{ return obj; }
set ...{ obj = value; }
}
#endregion
/**//// <summary>
/// 打开数据库连接
/// </summary>
public void Open()
...{ connect.Open(); }
/**//// <summary>
/// 关闭数据库连接
/// </summary>
public void Close()
...{ connect.Close(); }
/**//// <summary>
/// 无条件将连接设置为失效
/// </summary>
public void SetConnectionLost()
...{ enable = false; allot = false; }
/**//// <summary>
/// 被分配出去,线程安全的
/// </summary>
public void Repeat()
...{
lock (this)
...{
if (enable == false)//连接可用
throw new ResLostnExecption();//连接资源已经失效
if (allot == false)//是否可以被分配
throw new AllotExecption();//连接资源不可以被分配
if (use == true && isRepeat == false)
throw new AllotAndRepeatExecption();//连接资源已经被分配并且不允许重复引用
repeatNow++;//引用记数+1
useDegree++;//被使用次数+1
use = true;//被使用
}
}
/**//// <summary>
/// 被释放回来,线程安全的
/// </summary>
public void Remove()
...{
lock (this)
...{
if (enable == false)//连接可用
throw new ResLostnExecption();//连接资源已经失效
if (repeatNow == 0)
throw new RepeatIsZeroExecption();//引用记数已经为0
repeatNow--;//引用记数-1
if (repeatNow == 0)
use = false;//未使用
else
use = true;//使用中
}
}
/**//// <summary>
/// 释放资源
/// </summary>
public void Dispose()
...{
enable = false;
connect.Close();
connect = null;
}
}
上面代码的Java实现
/// <summary>
/// 连接池中的一个连接类型
/// </summary>
public class ConnStruct ...{
private boolean _enable = true; //是否失效
private boolean _isUse = false; //是否正在被使用中
private boolean _allot = true; //表示该连接是否可以被分配
private Date _createTime; //创建时间
private int _useDegree = 0; //被使用次数
private int _repeatNow = 0; //当前连接被重复引用多少
private boolean _isRepeat = true; //连接是否可以被重复引用,当被分配出去的连接可能使用事务时,该属性被标识为true
private int _connType; //连接类型
private Connection _connect = null; //连接对象
/** *//**
* 连接池中的连接
* @param dbc Connection 数据库连接
*/
public ConnStruct(Connection dbc) ...{
InitConnStruct(dbc, new Date());
}
/** *//**
* 连接池中的连接
* @param dbc Connection 数据库连接
* @param dt Date 连接创建时间
*/
public ConnStruct(Connection dbc, Date dt) ...{
InitConnStruct(dbc, dt);
}
/** *//**
* 连接池中的连接
* @param dbc Connection 数据库连接
* @param dt Date 连接创建时间
*/
private void InitConnStruct(Connection dbc, Date dt) ...{
_createTime = dt;
_connect = dbc;
}
//--------------------------------------------------------------------
/** *//**
* 得到一个值表示该连接是否可以被分配
* @return boolean 该连接是否可以被分配
*/
public boolean GetAllot() ...{
return _allot;
}
/** *//**
* 设置一个值表示该连接是否可以被分配
* @param value boolean true可以被分配,false不可以被分配
*/
public void SetAllot(boolean value) ...{
_allot = value;
}
/** *//**
* 得到当前连接是否失效
* @return boolean 得到当前连接是否失效;false表示失效,只读
*/
public boolean GetEnable() ...{
return _enable;
}
/** *//**
* 得到当前连接是否正在被使用中,只读
* @return boolean 当前连接是否正在被使用中
*/
public boolean GetIsUse() ...{
return _isUse;
}
/** *//**
* 得到连接创建时间,只读
* @return Date 创建时间
*/
public Date GetCreateTime() ...{
return _createTime;
}
/** *//**
* 得到被使用次数,只读
* @return int 得到被使用次数
*/
public int GetUseDegree() ...{
return _useDegree;
}
/** *//**
* 得到当前连接被重复引用多少,只读
* @return int 当前连接被重复引用多少
*/
public int GetRepeatNow() ...{
return _repeatNow;
}
/** *//**
* 得到该连接,只读
* @return Connection 连接
*/
public Connection GetConnection() ...{
return _connect;
}
/** *//**
* 得到连接是否可以被重复引用
* @return boolean 是否可以被重复引用
*/
public boolean GetIsRepeat() ...{
return _isRepeat;
}
/** *//**
* 设置连接是否可以被重复引用
* @param value boolean true可以被重复引用,false不可以被重复引用
*/
public void SetIsRepeat(boolean value) ...{
_isRepeat = value;
}
/** *//**
* 得到连接类型ConnectionPool.ConnType_*,只读
* @return int 连接类型
*/
public int GetConnType() ...{
return _connType;
}
/** *//**
* 关闭数据库连接
*/
public void Close() ...{
try ...{
_connect.close();
} catch (Exception e) ...{}
}
/** *//**
* 无条件将连接设置为失效
*/
public void SetConnectionLost() ...{
_enable = false;
_allot = false;
}
/** *//**
* 被分配出去,线程安全的
* @throws ResLostnExecption
* @throws AllotExecption
* @throws AllotAndRepeatExecption
*/
public synchronized void Repeat() throws ResLostnException, AllotException,
AllotAndRepeatException ...{
if (_enable == false) //连接可用
throw new ResLostnException(); //连接资源已经失效
if (_allot == false) //是否可以被分配
throw new AllotException(); //连接资源不可以被分配
if (_isUse == true && _isRepeat == false)
throw new AllotAndRepeatException(); //连接资源已经被分配并且不允许重复引用
_repeatNow++; //引用记数+1
_useDegree++; //被使用次数+1
_isUse = true; //被使用
}
/** *//**
* 被释放回来,线程安全的
* @throws ResLostnExecption 连接资源已经失效
* @throws RepeatIsZeroExecption 引用记数已经为0
*/
public synchronized void Remove() throws ResLostnException,
RepeatIsZeroException ...{
if (_enable == false) //连接可用
throw new ResLostnException(); //连接资源已经失效
if (_repeatNow == 0)
throw new RepeatIsZeroException(); //引用记数已经为0
_repeatNow--; //引用记数-1
if (_repeatNow == 0)
_isUse = false; //未使用
else
_isUse = true; //使用中
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose() ...{
_enable = false;
try ...{
_connect.close();
} catch (Exception e) ...{}
_connect = null;
}
}
lock (al_All)
...{
if (al_All.Count < createThreadProcessTemp_inside)
al_All.Add(CreateConnection(_connString, _connType));
else
join = true;
}
在模式0中线程将一直创建连接,直到被创建的连接达到最小连接数目,具体实现方法是将最小要创建的连接存入al_All,直到al_All的元素个数达到最小连接数才放出自己被阻止的消息。
lock (al_All)
...{
if (createThreadProcessTemp_inside != 0)
...{
createThreadProcessTemp_inside--;
al_All.Add(CreateConnection(_connString, _connType));
}
else
join = true;
}
在模式1中线程将一直创建连接,直到被创建的连接达到被要求的连接数目,具体实现方法是将最小要创建的连接存入createThreadProcessTemp_inside临时变量,然后每次创建一个连接就自减1直至到0时放出自己被阻止的消息。
以下是java的代码实现
/** *//**
* <p>创建线程类</p>
*/
private class CreateThreadProcess extends Thread ...{
public int createThreadMode = 0; //创建线程工作模式
public int createThreadProcessTemp = 0; //需要创建的连接数
public boolean createThreadProcessRun = false; //是否决定创建线程将继续工作,如果不继续工作则线程会将自己处于阻止状态
public void run() ...{
boolean join = false;
int createThreadProcessTemp_inside = createThreadProcessTemp;
_PoolState = PoolState_Initialize;
while (true)
...{
join = false;
_PoolState = PoolState_Run;
if (createThreadProcessRun == false)
...{//遇到终止命令
try ...{
this.join();//中断自己
} catch (Exception e) ...{/**//* */}
}
else
...{
if (createThreadMode == 0)
...{
//------------------------begin mode 创建模式
synchronized (al_All)
...{
if (al_All.size() < createThreadProcessTemp_inside)
al_All.add(CreateConnectionTemp(_ConnString,_DriveString,_userID,_password));
else
join = true;
}
//------------------------end mode
}
else if (createThreadMode == 1)
...{
//------------------------begin mode 增加模式
synchronized (al_All)
...{
if (createThreadProcessTemp_inside != 0)
...{
createThreadProcessTemp_inside--;
al_All.add(CreateConnectionTemp(_ConnString,_DriveString,_userID,_password));
}
else
join = true;
}
//------------------------end mode
}
else
join = true;
//-------------------------------------------------------------------------
if (join == true)
...{
UpdateAttribute();//更新属性
try ...{
createThreadProcessTemp = 0;
this.join(); //中断自己
} catch (Exception e) ...{
createThreadProcessTemp_inside = createThreadProcessTemp;
} //得到传入的变量
}
}
}
}
}
--------------------------------------------------------------------------------
小结:
文章主要讨论了如何编写一个高效的数据库连接池,包括多种数据库的支持引用记数的应用,截止文章发表为止,该连接池已经发现潜在问题诸多,比如在长时间运行中threadCheck和threadCreate线程的运行可能会不稳定,那导致的问题是致命的,关于解决问题么可能还要加入线程池?或者在启动停止服务时候创建销毁它们?如果你有更好的办法请发表评论。以便于我继续改进。
另一个以发现的问题就是连接池没有对数据库连接状态做跟踪,这个对于我是失败的问题,在设计它时直到2007年4月23日发布之前都没有考虑到这个因素,郁闷
另外我想说的就是这个小东西虽然不是很多代码,但是菜鸟的我也弄了2个来月所以请转贴时注明出处和作者。
还有一点想说的是即使把代码放上来也不见得是极其完整的代码,如果有想要原代码的朋友请发邮件到我的邮箱,邮箱下面有写。
作者:赵永春
QQ:253545925
Email:ta8210@126.com
home:ta8210.3322.org (本机做IIS故不定时开放)
此处为2007-08-16日更新说明:本文所讲连接池有所升级,具体请参看http://blog.csdn.net/ta8210/archive/2007/08/16/1746476.aspx
2008-09-25日追加更新 SVN地址,在使用SVN时可能需要你输入帐号密码,请在svnhost中注册一个帐号在下载源码。
java http://www.svnhost.cn/Project/Detail-1649.shtml
.net http://www.svnhost.cn/Project/Detail-1648.shtml
参考文献:《基于JDBC的数据库连接池高效管理策略》来源网络。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/ta8210/archive/2007/04/24/1582162.aspx