原由:许多用户可能在查询相同的数据库以获取相同的数据。在这些情况下,可以通过使应用程序共享到数据源的连接来提高应用程序的性能。否则,让每个用户打开和关闭单独的连接的开销会对应用程序性能产生不利影响。这样就有了连接池。
实现:
C#在连接字符串中设置连接池。
如果使用的是 OleDbConnection、OdbcConnection 或 OracleConnection 类,则连接池将由提供程序自动处理,所以您不必自己进行管理。
如果使用的是 SqlConnection 类,则连接池被隐式管理,但也提供选项允许您自己管理池。
连接使用Open()方法打开连接,这时候连接池就会初始化并建立设定的最小连接数。在使用完连接时一定要关闭连接,以便连接可以返回池。要关闭连接使用Close()
当连接数满了并且申请连接的时间超过设置连接等待的时间时,使用“异步进程”,对数据库进行异步操作,确保连接能够及时调用Close方法关闭连接,这样能大大减少正在使用的连接数。
当数据库操作和访问频繁的时候,减少创建连接和打开连接所耗的时间,提升数据库服务器的性能。 这里将详细分析C#数据库连接池。
使用C#数据库连接池
连接到数据库服务器通常由几个需要软长时间的步骤组成。必须建立物理通道(例如套接字或命名管道),必须与服务器进行初次连接,必须分析连接字符串信息,必须由服务器对连接进行身份验证,等等。
实际上,大部份的应用程序都是使用一个或几个不同的连接配置。当应用程序的数据量和访问量大的时候,这意味着在运行应用程序的过程中,许多相同的连接将反复地被打开和关闭,从而会引起数据库服务器效率低下甚至引发程序崩溃。为了确保应用程序的稳定和降低性能成本,我们可以在ADO.NET中使用称为连接池的优化方法来管理维护连接。
C#数据库连接池可以减少创建连接的次数。定义最小连接数(固定连接数),当用户在连接上调用Open,连接池就会检查池中是否有可用的连接。如果发现有连接可用,会将该连接返回给调用者,而不是创建新连接。应用程序在该连接上调用Close时,连接池会判断该连接是否在最小连接数之内,如果“是”会将连接回收到活动连接池中而不是真正关闭连接,否则将烧毁连接。连接返回到池中之后,即可在下一个Open调用中重复使用。
创建C#数据库连接池
以下示例使用C#连接SQL数据库:
classDbConn
{
//usingSystem.Data;
//usingSystem.Data.SqlClient;
privateconstintMaxPool=10;//最大连接数
privateconstintMinPool=5;//最小连接数
privateconstboolAsyn_Process=true;//设置异步访问数据库
privateconstboolMars=true;//在单个连接上得到和管理多个、仅向前引用和只读的结果集(ADO.NET2.0)
privateconstintConn_Timeout=15;//设置连接等待时间
privateconstintConn_Lifetime=15;//设置连接的生命周期
privatestringConnString="";//连接字符串
privateSqlConnectionSqlDrConn=null;//连接对象
publicDbConn()//构造函数
{
ConnString=GetConnString();
SqlDrConn=newSqlConnection(ConnString);
}
privatestringGetConnString()
{
return"server=localhost;"
+"integratedsecurity=sspi;"
+"database=pubs;"
+"MaxPoolSize="+MaxPool+";"
+"MinPoolSize="+MinPool+";"
+"ConnectTimeout="+Conn_Timeout+";"
+"ConnectionLifetime="+Conn_Lifetime+";"
+"AsynchronousProcessing="+Asyn_Process+";";
//+"MultipleActiveResultSets="+Mars+";";
}
publicDataTableGetDataReader(stringStrSql)//数据查询
{
//当连接处于打开状态时关闭,然后再打开,避免有时候数据不能及时更新
if(SqlDrConn.State==ConnectionState.Open)
{
SqlDrConn.Close();
}
try
{
SqlDrConn.Open();
SqlCommandSqlCmd=newSqlCommand(StrSql,SqlDrConn);
SqlDataReaderSqlDr=SqlCmd.ExecuteReader();
if(SqlDr.HasRows)
{
DataTabledt=newDataTable();
//读取SqlDataReader里的内容
dt.Load(SqlDr);
//关闭对象和连接
SqlDr.Close();
SqlDrConn.Close();
returndt;
}
returnnull;
}
catch(Exceptionex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
returnnull;
}
finally
{
SqlDrConn.Close();
}
}
}
通过调用SqlDrConn.Open()方法打开连接,这时候连接池就会初始化并建立设定的最小连接数。想更清楚了解到连接池的状况可以通过SQL的查询分析器执行存储过程sp_Who,它会列出当前的数据库进程,查看loginname、dbname可以区分用户的连接信息,但要注意的是登录查询分析器本身会使用两个连接,所以最好用另一个用户名登录查询分析器。使用此方法还有一个麻烦地方就是要经常按“执行查询”以更新进程信息。还有另一种方法个人认为较好的,通过控制面板→管理工具→性能,右击添加计算器,性能对象选择SQlServer:GeneralStatistics(常规统计)然后计算器选择UserConnections(用户连接)最后按“添加”就可以实时查看当前连接数。
到了这里,连接池已经实现了,但问题往往会出现在运行过程中。如连接池的连接数满了该怎样处理?在这里我们应该合理设置连接字符串中的ConnectTimeout属性和ConnectionLifetime属性(上面有解释)延长等待时间,尽可能地在每次使用完连接之后调用Close方法关闭连接。但从中也有没法避免的,当连接数满了并且申请连接的时间超过设置连接等待的时间时,程序将会引发InvalidOperationExceptio异常,我们可以通过捕获此异常向用户界面提示“系统正忙,请稍后再连接……”之类的信息来缓解这种情况。此外,也有另一种方法来解决这种情况,就是利用ADO.NET2.0新特性“异步进程”,对数据库进行异步操作,确保连接能够及时调用Close方法关闭连接,这样能大大减少正在使用的连接数。
使用方法:在连接字符串中加上AsynchronousProcessing=true表示使用异步处理操作。
当应用程序不再需要用到连接池的时候可以使用ClearPool或ClearAllPools方法清空连接池也可作重置连接池使用,方法如下:
SqlConnection.ClearPool(SqlConnectionconnection)清空关联的连接池
SqlConnection.ClearAllPools()清空所有连接池
调用上述方法,如果连接正在使用,连接池会做相应标记,等连接关闭时自动烧毁。
小结C#数据库连接池
优点:当数据库操作和访问频繁的时候,减少创建连接和打开连接所耗的时间,提升数据库服务器的性能。
缺点:数据库连接池中可能存在着多个没有被使用的连接一直连接着数据库,这意味着资源的浪费。
相关技术:
- 连接池
- 引用记数
- 多线程
- Timer类运行基理
- 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
ArrayListal_All
=
new
ArrayList();
//
实际连接
private
Hashtablehs_UseConn
=
new
Hashtable();
//
正在使用的连接
private
System.Timers.Timertime;
//
监视器记时器
private
ThreadthreadCreate;
//
创建线程
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
ArrayListal_All
=
new
ArrayList();
//
实际连接
private
Hashtablehs_UseConn
=
new
Hashtable();
//
正在使用的连接
private
CreateThreadProcessthreadCreate;
private
CheckThreadProcessthreadCheck;
当用户调用连接池的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(StringconnectionString,StringdriveString)
{
InitConnectionPool(connectionString,driveString,200,30,10,5);
}
public
ConnectionPool(StringconnectionString,StringdriveString,
int
maxConnection,
int
minConnection)
{
InitConnectionPool(connectionString,driveString,maxConnection,minConnection,10,5);
}
public
ConnectionPool(StringconnectionString,StringdriveString,
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,ConnTypeEnumcte)
{InitConnectionPool(connectionString,cte,200,30,10,5,5);}
public
ConnectionPool(
string
connectionString,ConnTypeEnumcte,
int
maxConnection,
int
minConnection)
{InitConnectionPool(connectionString,cte,maxConnection,minConnection,10,5,5);}
public
ConnectionPool(
string
connectionString,ConnTypeEnumcte,
int
maxConnection,
int
minConnection,
int
seepConnection,
int
keepConnection)
{InitConnectionPool(connectionString,cte,maxConnection,minConnection,seepConnection,keepConnection,5);}
public
ConnectionPool(
string
connectionString,ConnTypeEnumcte,
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,ConnTypeEnumcte,
int
maxConnection,
int
minConnection,
int
seepConnection,
int
keepConnection,
int
keepRealConnection)
{
if(cte==ConnTypeEnum.None)
thrownewConnTypeExecption();//参数不能是None
_ps=PoolState.UnInitialize;
this._connString=connectionString;
this._connType=cte;
this._minConnection=minConnection;
this._seepConnection=seepConnection;
this._keepRealConnection=keepRealConnection;
this._maxConnection=maxConnection;
this.time=newSystem.Timers.Timer(500);
this.time.Stop();
this.time.Elapsed+=newSystem.Timers.ElapsedEventHandler(time_Elapsed);
this.threadCreate=newThread(newThreadStart(createThreadProcess));
}
在InitConnectionPool方法中我们定义了连接池要连接的数据库类型ConnTypeEnumcte,和连接字符串stringconnectionString等类型,在方法最后注册了Timer类的Elapsed事件,在C#版中Timer类就充当threadCheck线程,而Elapsed事件就是线程threadCheck的执行方法体。
由于刚刚初始化连接池对象故threadCheck线程不需要运行所以在InitConnectionPool方法中将Timer关闭。下面是java实现:
private
void
InitConnectionPool(StringconnectionString,StringdriveString,
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=newCheckThreadProcess();
this.threadCheck.setDaemon(true);
this.threadCreate=newCreateThreadProcess();
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();
elseif(_ps==PoolState.Stop)
threadCreate.Interrupt();
else
thrownewPoolNotStopException();//服务已经运行或者未完全结束
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版本
/**
*启动连接池服务
*@throwsPoolNotStopException服务已经运行或者未完全结束
*/
public
void
StartServices()
throws
PoolNotStopException
{
synchronized(this){
threadCreate.createThreadMode=0;//工作模式0
threadCreate.createThreadProcessRun=true;
threadCreate.createThreadProcessTemp=_MinConnection;
if(_PoolState==PoolState_UnInitialize)
threadCreate.start();
elseif(_PoolState==PoolState_Stop)
threadCreate.interrupt();
else
thrownewPoolNotStopException();//服务已经运行或者未完全结束
threadCheck.StartTimer();
threadCheck.interrupt();//开始运行
}
}
停止服务StopService
///<summary>
///停止服务,线程安全
///</summary>
public
void
StopServices()
{StopServices(false);}
///<summary>
///停止服务,线程安全
///<paramname="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)
thrownewResCallBackException();//连接池资源未全部回收
}
time.Stop();
while(isThreadCheckRun){}//等待timer事件结束
createThreadProcessRun=false;
while(threadCreate.ThreadState!=ThreadState.WaitSleepJoin){}//等待可能存在的创建线程结束
lock(al_All)
{
for(inti=0;i<al_All.Count;i++)
((ConnStruct)al_All[i]).Dispose();
al_All.Clear();
}
_ps=PoolState.Stop;
}
else
thrownewPoolNotRunException();//服务未启动
}
UpdateAttribute();//更新属性
}
在这里我们使用了2层锁第一层锁我们是为了保证同一时间只能有一个线程调用这个方法,而lock(hs_UseConn)则是为了保护hs_UseConn的唯一访问hs_UseConn存放的就是申请连接时注册信息,needs是将未收回的连接资源也关闭,这将是危险的。认为可能你的程序正在使用此资源。
if(hs_UseConn.Count!=0)
thrownewResCallBackException();//连接池资源未全部回收。
这段代码是表示如果用户试图在连接没有全部释放回来时候将会引发异常,同时执行该方法的必要条件是连接池必须处在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版本的代码
/**
*停止服务,线程安全
*@throwsResCallBackException资源未全部回首
*@throwsPoolNotRunException服务没有运行
*/
public
void
StopServices()
throws
ResCallBackException,PoolNotRunException
{
StopServices(false);
}
/**
*停止服务,线程安全
*@paramneedsboolean是否必须退出;如果指定为false与StartServices()功能相同,如果指定为true。将未收回的连接资源关闭,这将是危险的。认为可能你的程序正在使用此资源。
*@throwsResCallBackException服务未启动
*@throwsPoolNotRunException服务没有运行
*/
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)
thrownewResCallBackException();//连接池资源未全部回收
}
threadCheck.StopTimer();
while(threadCreate.getState()!=Thread.State.WAITING){}//等待threadCreate事件结束
threadCreate.createThreadProcessRun=false;
while(threadCreate.getState()!=Thread.State.WAITING){}//等待可能存在的创建线程结束
synchronized(al_All){
for(inti=0;i<al_All.size();i++)
((ConnStruct)al_All.get(i)).Dispose();
al_All.clear();
}
_PoolState=PoolState_Stop;
}else
thrownewPoolNotRunException();//服务未启动
}
UpdateAttribute();//更新属性
}
申请GetConnectionFormPool
///<summary>
///在连接池中申请一个连接,使用None级别,线程安全
///</summary>
///<paramname="gui">发起者</param>
///<returns>返回申请到的连接</returns>
public
DbConnectionGetConnectionFormPool(
object
key)
{returnGetConnectionFormPool(key,ConnLevel.None);}
///<summary>
///在连接池中申请一个连接,线程安全
///</summary>
///<paramname="key">申请者</param>
///<paramname="cl">申请的连接级别</param>
///<returns>返回申请到的连接</returns>
public
DbConnectionGetConnectionFormPool(
object
key,ConnLevelcl)
{
lock(this)
{
if(_ps!=PoolState.Run)
thrownewStateException();//服务状态错误
if(hs_UseConn.Count==MaxConnectionFormPool)
thrownewPoolFullException();//连接池已经饱和,不能提供连接
if(hs_UseConn.ContainsKey(key))
thrownewKeyExecption();//一个key对象只能申请一个连接
if(cl==ConnLevel.ReadOnly)
returnGetConnectionFormPool_ReadOnly(key);//ReadOnly级别
elseif(cl==ConnLevel.High)
returnGetConnectionFormPool_High(key);//High级别
elseif(cl==ConnLevel.None)
returnGetConnectionFormPool_None(key);//None级别
else
returnGetConnectionFormPool_Bottom(key);//Bottom级别
}
}
在向连接池申请一个连接资源时我们必须指定一个申请者,同时可以指定一个级别参数最后会根据不同的级别调用不同的策略,这些策略是在上面介绍过的4钟策略,下面不做过多的介绍。
(Java版本不在放出原因代码与C#完全相同)
下面将逐一放出4种策略的完整代码不做过多解释,其功能以在前文介绍过
///<summary>
///申请一个连接资源,只读方式,线程安全
///</summary>
///<paramname="key">申请者</param>
///<returns>申请到的连接对象</returns>
protected
DbConnectionGetConnectionFormPool_ReadOnly(
object
key)
{
ConnStructcs=null;
for(inti=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;
returnGetConnectionFormPool_Return(key,cs,ConnLevel.ReadOnly);//返回得到的连接
}
returnGetConnectionFormPool_Return(key,null,ConnLevel.ReadOnly);
}
///<summary>
///申请一个连接资源,优先级-高,线程安全
///</summary>
///<paramname="key">申请者</param>
///<returns>申请到的连接对象</returns>
protected
DbConnectionGetConnectionFormPool_High(
object
key)
{
ConnStructcs=null;
ConnStructcsTemp=null;
for(inti=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;
}
}
returnGetConnectionFormPool_Return(key,cs,ConnLevel.High);//返回最合适的连接
}
///<summary>
///申请一个连接资源,优先级-中,线程安全
///</summary>
///<paramname="key">申请者</param>
///<returns>申请到的连接对象</returns>
protected
DbConnectionGetConnectionFormPool_None(
object
key)
{
ArrayListal=newArrayList();
ConnStructcs=null;
for(inti=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)
returnGetConnectionFormPool_Return(key,null,ConnLevel.None);//发出异常
else
returnGetConnectionFormPool_Return(key,((ConnStruct)al[al.Count/2]),ConnLevel.None);//返回连接
}
///<summary>
///申请一个连接资源,优先级-低,线程安全
///</summary>
///<paramname="key">申请者</param>
///<returns>申请到的连接对象</returns>
protected
DbConnectionGetConnectionFormPool_Bottom(
object
key)
{
ConnStructcs=null;
ConnStructcsTemp=null;
for(inti=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;
}
}
returnGetConnectionFormPool_Return(key,cs,ConnLevel.Bottom);//返回最合适的连接
}
Java版本不在放出原因代码与C#完全相同
在上面代码中所有策略都调用GetConnectionFormPool_Return方法。具体是什么呢?让我们来看看
///<summary>
///返回DbConnection对象,同时做获得连接时的必要操作
///</summary>
///<paramname="key">key</param>
///<paramname="cs">ConnStruct对象</param>
///<paramname="cl">级别</param>
///<paramname="readOnly">是否为只读属性</param>
///<returns></returns>
private
DbConnectionGetConnectionFormPool_Return(
object
key,ConnStructcs,ConnLevelcl)
{
try
{
if(cs==null)
thrownewException();
cs.Repeat();
hs_UseConn.Add(key,cs);
if(cl==ConnLevel.ReadOnly)
{
cs.Allot=false;
cs.IsRepeat=false;
}
}
catch(Exceptione)
{
thrownewOccasionExecption();//连接资源耗尽,或错误的访问时机。
}
finally
{
UpdateAttribute();//更新属性
}
returncs.Connection;
}
该方法就是返回DbConnection对象,同时将获得的连接引用记数+1,在程序的第一行进行了一下内部错误判断如果是一个空的cs则抛出一个异常,该异常由方法体内接收并且将异常统一包装为OccasionExecption类型异常。hs_UseConn.Add(key,cs);是将被申请到的连接资源注册到连接池。同时也确定这个被申请走的连接是不是以只读方式申请的,如果是则将这个连接锁定。
cs.Allot=false;//设置该连接不能再次被分配
cs.IsRepeat=false;//设置连接不能使用引用记数技术
(Java版本不在放出原因代码与C#完全相同)
释放DisposeConnection
///<summary>
///释放申请的数据库连接对象,线程安全
///<paramname="key">key表示数据库连接申请者</param>
///</summary>
public
void
DisposeConnection(
object
key)
{
lock(hs_UseConn)
{
ConnStructcs=null;
if(_ps==PoolState.Run)
{
if(!hs_UseConn.ContainsKey(key))
thrownewNotKeyExecption();//无法释放,不存在的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
thrownewPoolNotRunException();//服务未启动
}
UpdateAttribute();//更新属性
}
在释放资源中可以说是上面申请资源的反方向操作其中不同点在于
if(cs.Enable==true)
cs.Allot=true;
这段话的意思是如果连接对象已经失效则不更改会不会再次被分配的属性如果仍然有效则更改为会再次被分配,因为被包装后的Connection不会自己知道自己是否已经超过引用记数所要求的最大上限。
(Java版本不在放出原因代码与C#完全相同)
如何更新属性
///<summary>
///更新属性
///</summary>
private
void
UpdateAttribute()
{
inttemp_readOnlyFormPool=0;//连接池已经分配多少只读连接
inttemp_potentRealFormPool=0;//连接池中存在的实际连接数(有效的实际连接)
inttemp_spareRealFormPool=0;//空闲的实际连接
inttemp_useRealFormPool=0;//已分配的实际连接
inttemp_spareFormPool=MaxConnectionFormPool;//目前可以提供的连接数
//---------------------------------
lock(hs_UseConn)
{
_useFormPool=hs_UseConn.Count;
}
//---------------------------------
ConnStructcs=null;
intn=0;
lock(al_All)
{
_realFormPool=al_All.Count;
for(inti=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>
///<paramname="cs">被测试的ConnStruct</param>
private
void
TestConnStruct(ConnStructcs)
{
//此次被分配出去的连接是否在此次之后失效
if(cs.UseDegree==_maxRepeatDegree)
cs.SetConnectionLost();//超过使用次数
if(cs.CreateTime.AddMinutes(_exist).Ticks<=DateTime.Now.Ticks)
cs.SetConnectionLost();//连接超时
}
Java版本
/**
*测试ConnStruct是否过期
*@paramcsConnStruct被测试的ConnStruct
*/
private
void
TestConnStruct(ConnStructcs)
{
//此次被分配出去的连接是否在此次之后失效
if(cs.GetUseDegree()==_MaxRepeatDegree)
cs.SetConnectionLost();//超过使用次数
Calendarc=Calendar.getInstance();
c.setTime(cs.GetCreateTime());
c.set(Calendar.MINUTE,c.get(Calendar.MINUTE)+_Exist);
if(c.getTime().after(newDate()))
cs.SetConnectionLost();//连接超时
}
使用线程管理连接池
threadCreate
前面已经很多次的提到threadCreate和threadCheck。那它们2究竟怎么运行的呢?还是先让我们看一看threadCreate。
///<summary>
///创建线程
///</summary>
private
void
createThreadProcess()
{
booljoin=false;
intcreateThreadProcessTemp_inside=createThreadProcessTemp;
_ps=PoolState.Initialize;
while(true)
{
join=false;
_ps=PoolState.Run;
if(createThreadProcessRun==false)
{//遇到终止命令
try{threadCreate.Join();}
catch(Exceptione){}
}
else
{
if(createThreadMode==0)
{
//------------------------beginmode创建模式
...............在下面中说明
//------------------------endmode
}
elseif(createThreadMode==1)
{
//------------------------beginmode增加模式
...............在下面中说明
//------------------------endmode
}
else
join=true;
//-------------------------------------------------------------------------
if(join==true)
{
UpdateAttribute();//更新属性
try
{
createThreadProcessTemp=0;
threadCreate.Join();
}
catch(Exceptione)
{createThreadProcessTemp_inside=createThreadProcessTemp;}//得到传入的变量
}
}
}
}
在线程中每次执行循环前都要判断自己是不是被要求终止,如果自己被要求终止则无条件将自己立即阻塞上,注意线程仅仅被阻塞没有被终止。
在下面一组createThreadMode运行模式判断中如果出现不属于0或者1的模式则放出自己被阻止的信息。
在最后线程即将被阻止时将执行更新属性操作并且将自己的运行参数设置为0,因为当运行参数为0时即使线程被唤醒,它也将不会创建任何连接,最终自己还是被阻止。
模式0模式1
threadCheck
ConnStructcs
=
null
;
time.Stop();
//
关闭自己
isThreadCheckRun
=
true
;
//
如果正在执行创建连接则退出
if
(threadCreate.ThreadState
!=
ThreadState.WaitSleepJoin)
return
;
//
------------------------------------------------------
lock
(al_All)
{
intn=0;
for(inti=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)
returnseepNum;
else
returnmaxNum-nowNum;
}
下面是threadCheck的java版,因为前文说了在Java下没有Timer类有的Timer类还仅仅在AWT下工作,因此我们要先做一个Timer类,具体如下
/**
*<p>检测事件</p>
*/
private
class
CheckThreadProcess
extends
Thread
{
privatelong_Interval=100;//执行间隔
privatebooleantimeSize=false;
/**
*启动记时器
*/
publicvoidStartTimer()
{
timeSize=true;
if(this.getState()==Thread.State.NEW)
this.start();
elseif(this.getState()==Thread.State.WAITING)
this.interrupt();
}
/**
*停止记时器
*/
publicvoidStopTimer()
{
timeSize=false;
while(this.getState()!=Thread.State.WAITING){}
}
publicvoidaRun()
{
}
publicvoidrun(){
while(true){
try{
this.join(_Interval);
if(timeSize==true)
aRun();
else
this.join();
}catch(InterruptedExceptionex1){}
}
}
/**
*设置执行时间间隔
*@paramvaluedouble时间间隔
*/
publicvoidsetInterval(longvalue){
_Interval=value;
}
/**
*获得执行时间间隔
*@returndouble时间间隔
*/
publiclonggetInterval(){
return_Interval;
}
}
其中aRun方法就是threadCheck在C#下的处理过程代码如下
public
void
aRun()
{
ConnStructcs=null;
//如果正在执行创建连接则退出
if(threadCreate.getState()!=Thread.State.WAITING)
return;
//------------------------------------------------------
synchronized(al_All)
{
for(inti=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>
///<paramname="dbc">数据库连接</param>
///<paramname="cte">连接类型</param>
publicConnStruct(DbConnectiondbc,ConnTypeEnumcte)
{
createTime=DateTime.Now;
connect=dbc;
connType=cte;
}
///<summary>
///连接池中的连接
///</summary>
///<paramname="dt">连接创建时间</param>
///<paramname="dbc">数据库连接</param>
///<paramname="cte">连接类型</param>
publicConnStruct(DbConnectiondbc,ConnTypeEnumcte,DateTimedt)
{
createTime=dt;
connect=dbc;
connType=cte;
}
//--------------------------------------------------------------------
privateboolenable=true;//是否失效
privatebooluse=false;//是否正在被使用中
privateboolallot=true;//表示该连接是否可以被分配
privateDateTimecreateTime=DateTime.Now;//创建时间
privateintuseDegree=0;//被使用次数
privateintrepeatNow=0;//当前连接被重复引用多少
privateboolisRepeat=true;//连接是否可以被重复引用,当被分配出去的连接可能使用事务时,该属性被标识为true
privateConnTypeEnumconnType=ConnTypeEnum.None;//连接类型
privateDbConnectionconnect=null;//连接对象
privateobjectobj=null;//连接附带的信息
属性部分
///<summary>
///打开数据库连接
///</summary>
publicvoidOpen()
{connect.Open();}
///<summary>
///关闭数据库连接
///</summary>
publicvoidClose()
{connect.Close();}
///<summary>
///无条件将连接设置为失效
///</summary>
publicvoidSetConnectionLost()
{enable=false;allot=false;}
///<summary>
///被分配出去,线程安全的
///</summary>
publicvoidRepeat()
{
lock(this)
{
if(enable==false)//连接可用
thrownewResLostnExecption();//连接资源已经失效
if(allot==false)//是否可以被分配
thrownewAllotExecption();//连接资源不可以被分配
if(use==true&&isRepeat==false)
thrownewAllotAndRepeatExecption();//连接资源已经被分配并且不允许重复引用
repeatNow++;//引用记数+1
useDegree++;//被使用次数+1
use=true;//被使用
}
}
///<summary>
///被释放回来,线程安全的
///</summary>
publicvoidRemove()
{
lock(this)
{
if(enable==false)//连接可用
thrownewResLostnExecption();//连接资源已经失效
if(repeatNow==0)
thrownewRepeatIsZeroExecption();//引用记数已经为0
repeatNow--;//引用记数-1
if(repeatNow==0)
use=false;//未使用
else
use=true;//使用中
}
}
///<summary>
///释放资源
///</summary>
publicvoidDispose()
{
enable=false;
connect.Close();
connect=null;
}
}
上面代码的Java实现
//
/<summary>
//
/连接池中的一个连接类型
//
/</summary>
public
class
ConnStruct
{
privateboolean_enable=true;//是否失效
privateboolean_isUse=false;//是否正在被使用中
privateboolean_allot=true;//表示该连接是否可以被分配
privateDate_createTime;//创建时间
privateint_useDegree=0;//被使用次数
privateint_repeatNow=0;//当前连接被重复引用多少
privateboolean_isRepeat=true;//连接是否可以被重复引用,当被分配出去的连接可能使用事务时,该属性被标识为true
privateint_connType;//连接类型
privateConnection_connect=null;//连接对象
/**
*连接池中的连接
*@paramdbcConnection数据库连接
*/
publicConnStruct(Connectiondbc){
InitConnStruct(dbc,newDate());
}
/**
*连接池中的连接
*@paramdbcConnection数据库连接
*@paramdtDate连接创建时间
*/
publicConnStruct(Connectiondbc,Datedt){
InitConnStruct(dbc,dt);
}
/**
*连接池中的连接
*@paramdbcConnection数据库连接
*@paramdtDate连接创建时间
*/
privatevoidInitConnStruct(Connectiondbc,Datedt){
_createTime=dt;
_connect=dbc;
}
//--------------------------------------------------------------------
/**
*得到一个值表示该连接是否可以被分配
*@returnboolean该连接是否可以被分配
*/
publicbooleanGetAllot(){
return_allot;
}
/**
*设置一个值表示该连接是否可以被分配
*@paramvaluebooleantrue可以被分配,false不可以被分配
*/
publicvoidSetAllot(booleanvalue){
_allot=value;
}
/**
*得到当前连接是否失效
*@returnboolean得到当前连接是否失效;false表示失效,只读
*/
publicbooleanGetEnable(){
return_enable;
}
/**
*得到当前连接是否正在被使用中,只读
*@returnboolean当前连接是否正在被使用中
*/
publicbooleanGetIsUse(){
return_isUse;
}
/**
*得到连接创建时间,只读
*@returnDate创建时间
*/
publicDateGetCreateTime(){
return_createTime;
}
/**
*得到被使用次数,只读
*@returnint得到被使用次数
*/
publicintGetUseDegree(){
return_useDegree;
}
/**
*得到当前连接被重复引用多少,只读
*@returnint当前连接被重复引用多少
*/
publicintGetRepeatNow(){
return_repeatNow;
}
/**
*得到该连接,只读
*@returnConnection连接
*/
publicConnectionGetConnection(){
return_connect;
}
/**
*得到连接是否可以被重复引用
*@returnboolean是否可以被重复引用
*/
publicbooleanGetIsRepeat(){
return_isRepeat;
}
/**
*设置连接是否可以被重复引用
*@paramvaluebooleantrue可以被重复引用,false不可以被重复引用
*/
publicvoidSetIsRepeat(booleanvalue){
_isRepeat=value;
}
/**
*得到连接类型ConnectionPool.ConnType_*,只读
*@returnint连接类型
*/
publicintGetConnType(){
return_connType;
}
/**
*关闭数据库连接
*/
publicvoidClose(){
try{
_connect.close();
}catch(Exceptione){}
}
/**
*无条件将连接设置为失效
*/
publicvoidSetConnectionLost(){
_enable=false;
_allot=false;
}
/**
*被分配出去,线程安全的
*@throwsResLostnExecption
*@throwsAllotExecption
*@throwsAllotAndRepeatExecption
*/
publicsynchronizedvoidRepeat()throwsResLostnException,AllotException,
AllotAndRepeatException{
if(_enable==false)//连接可用
thrownewResLostnException();//连接资源已经失效
if(_allot==false)//是否可以被分配
thrownewAllotException();//连接资源不可以被分配
if(_isUse==true&&_isRepeat==false)
thrownewAllotAndRepeatException();//连接资源已经被分配并且不允许重复引用
_repeatNow++;//引用记数+1
_useDegree++;//被使用次数+1
_isUse=true;//被使用
}
/**
*被释放回来,线程安全的
*@throwsResLostnExecption连接资源已经失效
*@throwsRepeatIsZeroExecption引用记数已经为0
*/
publicsynchronizedvoidRemove()throwsResLostnException,
RepeatIsZeroException{
if(_enable==false)//连接可用
thrownewResLostnException();//连接资源已经失效
if(_repeatNow==0)
thrownewRepeatIsZeroException();//引用记数已经为0
_repeatNow--;//引用记数-1
if(_repeatNow==0)
_isUse=false;//未使用
else
_isUse=true;//使用中
}
///<summary>
///释放资源
///</summary>
publicvoidDispose(){
_enable=false;
try{
_connect.close();
}catch(Exceptione){}
_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
{
publicintcreateThreadMode=0;//创建线程工作模式
publicintcreateThreadProcessTemp=0;//需要创建的连接数
publicbooleancreateThreadProcessRun=false;//是否决定创建线程将继续工作,如果不继续工作则线程会将自己处于阻止状态
publicvoidrun(){
booleanjoin=false;
intcreateThreadProcessTemp_inside=createThreadProcessTemp;
_PoolState=PoolState_Initialize;
while(true)
{
join=false;
_PoolState=PoolState_Run;
if(createThreadProcessRun==false)
{//遇到终止命令
try{
this.join();//中断自己
}catch(Exceptione){/**/}
}
else
{
if(createThreadMode==0)
{
//------------------------beginmode创建模式
synchronized(al_All)
{
if(al_All.size()<createThreadProcessTemp_inside)
al_All.add(CreateConnectionTemp(_ConnString,_DriveString,_userID,_password));
else
join=true;
}
//------------------------endmode
}
elseif(createThreadMode==1)
{
//------------------------beginmode增加模式
synchronized(al_All)
{
if(createThreadProcessTemp_inside!=0)
{
createThreadProcessTemp_inside--;
al_All.add(CreateConnectionTemp(_ConnString,_DriveString,_userID,_password));
}
else
join=true;
}
//------------------------endmode
}
else
join=true;
//-------------------------------------------------------------------------
if(join==true)
{
UpdateAttribute();//更新属性
try{
createThreadProcessTemp=0;
this.join();//中断自己
}catch(Exceptione){
createThreadProcessTemp_inside=createThreadProcessTemp;
}//得到传入的变量
}
}
}
}
}
小结:
文章主要讨论了如何编写一个高效的数据库连接池,包括多种数据库的支持引用记数的应用,截止文章发表为止,该连接池已经发现潜在问题诸多,比如在长时间运行中threadCheck和threadCreate线程的运行可能会不稳定,那导致的问题是致命的,关于解决问题么可能还要加入线程池?或者在启动停止服务时候创建销毁它们?如果你有更好的办法请发表评论。以便于我继续改进。
另一个以发现的问题就是连接池没有对数据库连接状态做跟踪,这个对于我是失败的问题,在设计它时直到2007年4月23日发布之前都没有考虑到这个因素,郁闷
另外我想说的就是这个小东西虽然不是很多代码,但是菜鸟的我也弄了2个来月所以请转贴时注明出处和作者。
还有一点想说的是即使把代码放上来也不见得是极其完整的代码,如果有想要原代码的朋友请发邮件到我的邮箱,邮箱下面有写。
作者:赵永春
QQ:253545925(长期潜水)
Email:[email protected]
home:ta8210.3322.org(本机做IIS故不定时开放)
参考文献:《基于JDBC的数据库连接池高效管理策略》来源网络。