1、简单的数据库连接池
简介:
使用等待超时模式来构造一个简单的数据库连接池。
数据库连接池支持如下功能
普通获取连接无超时时间
超时获取连接,超时时间之后返回null
使用连接
释放连接
记录获取连接和为获取连接的次数,统计连接池的性能
超时等待模式
在实现数据库连接池功能之前,我们先来回顾一下上一章的等待/通知经典范式。即加锁、条件循环和处理逻辑三个步骤,但是常规的等待/通知无法做到超时等待,因此我们做一些小改动来实现一个超时等待的等待/通知范式。
改动方式:
定义等待时间T
计算超时返回的时间now() + T
当结果不满足或者超时时间未到,wait()
伪代码:
//当前对象加锁publicsynchronizedObjectget(longmills)throwsInterruptedException{longfuture=System.currentTimeMills+mills;longremaining=mills;//当结果不满足并且超时时间大于0时继续等待(即使被唤醒)while((result==null)&&remainig>0){wait(remaining);remaining=future-System.currentTimeMills;}returnresult;}
连接池代码及功能介绍
连接池初始大小为10,也可以指定大小。使用LinkedList队列来做连接池管理,队列尾部插入连接,队列头部获取连接。connection()/connection(long)支持普通获取连接无超时时间和超时等待获取连接,超时获取在指定时间内没有获取到连接将会返回null;releaseConnection(connection)释放连接后,连接归队并通知等待在该连接池上的线程获取连接。
packagecom.lizba.p3;importjava.sql.Connection;importjava.util.LinkedList;/***<p>*数据库连接池*</p>**@Author:Liziba*@Date:2021/6/1721:13*/publicclassConnectionPool{/**默认连接池大小*/privatestaticfinalintDEFAULT_SIZE=10;/**连接池存储容器*/privateLinkedList<Connection>pool=newLinkedList<>();publicConnectionPool(intinitialSize){//参数不正确默认初始化10initialSize=initialSize<=0?DEFAULT_SIZE:initialSize;for(inti=0;i<initialSize;i++){pool.addLast(ConnectionDriver.createConnection());}}/***释放连接,重回连接池,并且通知等待中的消费者*@paramconnection*/publicvoidreleaseConnection(Connectionconnection){if(connection!=null){synchronized(pool){pool.addLast(connection);//通知消费者pool.notifyAll();}}}/***直接获取连接,不知道超时时间,则会一直等待**@return*@throwsInterruptedException*/publicConnectionconnection()throwsInterruptedException{returnconnection(0);}/***指定获取连接时间**@parammills*@return*@throwsInterruptedException*/publicConnectionconnection(longmills)throwsInterruptedException{synchronized(pool){if(mills<=0){while(pool.isEmpty()){pool.wait();}returnpool.removeFirst();}else{longfuture=System.currentTimeMillis()+mills;longremaining=mills;while(pool.isEmpty()&&remaining>0){pool.wait(mills);remaining=future-System.currentTimeMillis();}returnpool.isEmpty()?null:pool.removeFirst();}}}}
连接驱动模拟:
Connection是一个接口,我们通过动态代理来创建Connection,当执行Connection的commit方法时,通过 TimeUnit.MILLISECONDS.sleep(200);休眠线程来模拟执行事务提交。
packagecom.lizba.p3;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.lang.reflect.Proxy;importjava.sql.Connection;importjava.util.concurrent.TimeUnit;/***<p>*数据库连接驱动动态代理模拟类*</p>**@Author:Liziba*@Date:2021/6/1716:57*/publicclassConnectionDriver{privatestaticfinalStringCOMMIT_OP="commit";staticclassConnectionHandlerimplementsInvocationHandler{@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{//commit时睡眠200msif(method.getName().equals(COMMIT_OP)){TimeUnit.MILLISECONDS.sleep(200);}returnnull;}}publicstaticfinalConnectioncreateConnection(){return(Connection)Proxy.newProxyInstance(ConnectionDriver.class.getClassLoader(),newClass<?>[]{Connection.class},newConnectionHandler());}}
客户端测试:
客户端使用多线程模拟对数据库发起多个连接,并通过统计获取和未获取的次数来计算在不同线程池大小和客户端连接数的情况下,客户端从线程池获取线程的成功和失败的次数。
packagecom.lizba.p3;importjava.sql.Connection;importjava.sql.SQLException;importjava.util.concurrent.CountDownLatch;importjava.util.concurrent.atomic.AtomicInteger;/***<p>*连接池测试,使用CountdownLatch确保connectionThread能同时执行,并发获取连接*</p>**@Author:Liziba*@Date:2021/6/1717:51*/publicclassPoolTest{/**初始化线程池*/privatestaticConnectionPoolpool=newConnectionPool(10);/**保证所有线程都一起执行*/privatestaticCountDownLatchstart=newCountDownLatch(1);/**保证main线程执行在所有线程执行完之后再往后执行*/privatestaticCountDownLatchend;publicstaticvoidmain(String[]args)throwsInterruptedException{//定义获取连接的线程数量intthreadSize=10;//定义每个线程获取连接的次数intcount=50;end=newCountDownLatch(threadSize);AtomicIntegergetConnectionCount=newAtomicInteger();AtomicIntegernotGetConnectionCount=newAtomicInteger();for(inti=0;i<threadSize;i++){Threadt=newThread(newRunner(count,getConnectionCount,notGetConnectionCount),"connectionThread");t.start();}start.countDown();end.await();System.out.println("执行次数"+(threadSize*count));System.out.println("获取连接次数"+getConnectionCount);System.out.println("未获取连接次数"+notGetConnectionCount);}staticclassRunnerimplementsRunnable{privateintcount;privateAtomicIntegergetConnectionCount;privateAtomicIntegernotGetConnectionCount;publicRunner(intcount,AtomicIntegergetConnectionCount,AtomicIntegernotGetConnectionCount){this.count=count;this.getConnectionCount=getConnectionCount;this.notGetConnectionCount=notGetConnectionCount;}@Overridepublicvoidrun(){try{//等待知道主线程执行完start.countDown();start.await();}catch(InterruptedExceptione){e.printStackTrace();}//循环获取连接while(count>0){try{Connectionconnection=pool.connection(1000);if(connection!=null){try{connection.createStatement();connection.commit();}finally{//释放连接pool.releaseConnection(connection);//记录获取的数量getConnectionCount.incrementAndGet();}}else{//记录未获取到的数量notGetConnectionCount.incrementAndGet();}}catch(InterruptedException|SQLExceptione){e.printStackTrace();}finally{count--;}}end.countDown();}}}
测试结果统计:
笔者CPU(AMD Ryzen 5 3600 6-Core Processor),内存16G。
总结:
在数据量资源固定的情况下,随着并发连接数的上升,获取连接失败的比率会升高。此处超时连接的设计,在实际开发运用过程中,有利于减少程序阻塞时间,避免在连接一直不可用的情况下导致服务不可用,通过返回null,程序员在设计系统时可以做响应的容错措施或者服务降级等处理。