java连接池实现
一、池化技术和连接池
谈谈池化技术 - 简单点来说,就是预先保存好大量的资源,这些是可复用的资源,你需要的时候给你。
对于线程,内存,oracle的连接对象等等,这些都是资源,程序中当你创建一个线程或者在堆上申请一块内存时,
都涉及到很多系统调用,也是非常消耗CPU的,如果你的程序需要很多类似的工作线程或者需要频繁的申请释放小块内存,
如果没有在这方面进行优化,那很有可能这部分代码将会成为影响你整个程序性能的瓶颈。池化技术主要有线程池,内存池,
连接池,对象池等等,对象池就是提前创建很多对象,将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用。
再来看看我们今天所要关注的连接池:
连接池比较典型的应用有数据库的连接池(下面我们将写一个redis nosql的连接池简单实现一下,创建可复用的连接对象)
数据库连接是一种关键的有限的昂贵的资源,连接是有限的,这一点在多用户的网页应用程序中体现得尤为突出。 一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的 性能低下。
数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库联接对象),由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。
连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
1) 最小连接数是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费;
2) 最大连接数是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求将被加入到等待队列中,这会影响之后的数据库操作。
连接池的必须功能:
获取
释放
限制
关闭
二、代码实现
我们先来写测试连接池的测试程序,我们模拟30个线程并发,同时去访问我们的redis连接池
Test.java
package com.zzq.core.test;
import java.util.concurrent.CountDownLatch;
import redis.clients.jedis.Jedis;
import com.zzq.core.pool.ConnectionPool;
import com.zzq.core.pool.ConnectionPoolImpl;
public class Test {
private static int threadCount = 30;
private final static CountDownLatch COUNT_DOWN_LATCH = new CountDownLatch(threadCount); //为保证30个线程同时并发运行
public static void main(String[] args) {
final ConnectionPool pool = new ConnectionPoolImpl();
//连接池最大连接数和获取连接的超时时间
pool.init(10, 2000L);
for(int i = 0 ; i < threadCount ; i++){//循环开30个线程
new Thread(new Runnable() {
public void run() {
int i = 0;
while (i < 10) {//每个线程里循环十次获取连接
i++;
Jedis jedis = null;
try {
COUNT_DOWN_LATCH.countDown();//每次减一
COUNT_DOWN_LATCH.await(); //此处等待状态,为了让30个线程同时进行
jedis = pool.borrowResource();
jedis.get("a");
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
pool.release(jedis); //释放连接
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}).start();
}
}
}
然后是核心代码 (需要jedis的jar包)
package com.zzq.core.pool;
import redis.clients.jedis.Jedis;
public interface ConnectionPool {
/**
* zhouzhongqing
* 2017年9月16日12:28:56
* 初始化线程池
* max 最大连接数
* maxWait 最大等待时间
* */
void init(int maxActive ,long maxWait);
/**
* zhouzhongqing
* 2017年9月16日12:29:11
* 获取连接
* */
Jedis borrowResource()throws Exception;
/****
* zhouzhongqing
* 2017-9-16 12:30:38
* 释放连接
* **/
void release(Jedis jedis)throws Exception;
/****
* zhouzhongqing
* 2017-9-16 12:30:59
* 关闭
* **/
void close();
}
实现类:
package com.zzq.core.pool;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import redis.clients.jedis.Jedis;
public class ConnectionPoolImpl implements ConnectionPool {
//是否关闭
AtomicBoolean isClosed = new AtomicBoolean(false);
//队列实现连接 对象存储
LinkedBlockingQueue<Jedis> idle; //空闲队列
LinkedBlockingQueue<Jedis> busy; //繁忙队列
//大小控制连接数量
AtomicInteger activeSize = new AtomicInteger(0);
//记录连接被创建的次数
AtomicInteger createCounter = new AtomicInteger(0);
int maxActive;
long maxWait;
public void init(int maxActive, long maxWait) {
this.maxActive = maxActive;
this.maxWait = maxWait;
idle = new LinkedBlockingQueue<Jedis>();
busy = new LinkedBlockingQueue<Jedis>();
}
public Jedis borrowResource() throws Exception {
Jedis jedis = null;
long now = System.currentTimeMillis();//获取连接的开始时间
while(null == jedis){
//从空闲队列中获取一个
jedis = idle.poll();
if(null != jedis){
//如果空闲队列里有连接,直接是被复用,再将此连接移动到busy (繁忙)队列中
busy.offer(jedis);
System.out.println("从空闲队列里拿到连接");
return jedis;
}
//如果空闲队列里没有连接,就判断是否超出连接池大小,不超出就创建一个
if(activeSize.get() < maxActive){//多线程并发
//先加再判断
if(activeSize.incrementAndGet() <= maxActive){
//创建jedis连接
jedis = new Jedis("127.0.0.1", 6379);
System.out.println("连接被创建的次数:" +createCounter.incrementAndGet());
//存入busy队列
busy.offer(jedis);
return jedis;
}else{
//加完后超出大小再减回来
activeSize.decrementAndGet();
}
}
//如果前面2个都不能拿到连接,那就在我们设置的最大等待超时时间内,等待别人释放连接
try {
//等待别人释放得到连接,同时也有最长的等待时间限制
jedis = idle.poll(maxWait - (System.currentTimeMillis() - now), TimeUnit.MILLISECONDS);
} catch (Exception e) {
throw new Exception("等待异常 ... ");
}
if(null == jedis ) {
//判断是否超时
if( (System.currentTimeMillis() - now) >= maxWait ){
throw new Exception("timeout ... ");
}else{
continue;
}
}else{
//存入busy队列
busy.offer(jedis);
}
}
return jedis;
}
public void release(Jedis jedis) throws Exception {
if(null == jedis ) {
System.out.println("释放 的jedis为空");
return;
}
if(busy.remove(jedis)){
idle.offer(jedis);
}else{
//如果释放不成功,则减去一个连接,在创建的时候可以自动补充
activeSize.decrementAndGet();
throw new Exception("释放jedis异常");
}
}
public void close() {
if(isClosed.compareAndSet(false, true)){
LinkedBlockingQueue<Jedis> pool = idle;
while(pool.isEmpty()){
Jedis jedis = pool.poll();
jedis.close();
if(pool == idle && pool.isEmpty() ){
pool = busy;
}
}
}
}
}
运行代码时先运行redis,我这边为了方便用了个Windows的
运行结果:
连接最大被创建10,其他的连接是空闲队列得到的或者是在设置的最长等待时间从空闲队列得到到,已经达到了我们想要的效果了。
附上源码和安装包(Windows redis和redis-desktop-manager) http://download.csdn.net/download/baidu_19473529/9981750
推荐看看手写精简版的java线程池,这两篇都是池化技术,可能更容易理解线程池和连接池的区别和相近之处等等。代码或者我理解上有问题的地方,还请不吝指正。