咱们常常会遇到这样那样的链接未关闭的问题,链接没有及时关闭致使的直接后果就是内存泄漏直至down机。咱们也都知道解决的方式,可是在解决了问题以后常常会思考为何会这样呢?链接close()掉,而后在建立不是很浪费cpu等系统资源嘛?有没有更好的方法解决呢?你们也常常听到链接池、线程池之类的线程、池的概念,那么究竟这些概念与咱们的链接有什么关系呢?java
下面我就想就上面的问题谈谈个人一点浅见,请你们批评指正。web
你们都知道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时有关于链接池最大最小数目的配置。呵呵,道理就在这。
那么,咱们常常面对的链接未关闭的问题致使的系统速度很慢的问题就很容易说明了,就是由于线程池已经达到了最大数目,没有可用的了。因此,其余操做只有等待的份,等待那些应用用完了,被垃圾回收了,才能释放出可用的链接。