JDBC的API设计中有一个DriverManager类,它提供了一个静态方法getConnection()该方法用于与数据库建立一个连接,返回一个Connection实例。
而在JDBC2.0版本的API中提供了一个新的接口:javax.sql.DataSource,该接口也提供抽象方法getConnection(),Druid基于该接口做了连接池的实现,即:DruidDataSource实现类。
以下内容将关注DruidDataSource的getConnection()接口实现,我们可以先找到javax.sql.DataSource接口:
简要流程
向下找到DruidDataSource实现类
和getConnection()的实现
我们看到getConnection()调用了一个内部方法,传入maxWait入参(最大等待时间),进入内部方法
getConnection()方法主要做了两步,1)连接池初始化操作;2)获取连接;
我们进入init方法看看初始化,初始化方法很长,这里关注以下两块内容
1、同步初始化连接
我们看到首先基于maxActive这里配置创建了一个Connection的数组,然后根据initialSize这个配置初始化了对应的Connection实例并在数组里存放。(这点和ThreadPoolExecutor线程池不一样,线程池是一个一个创建,连接池是首次直接全部创建,后续不再调用init方法);
2、线程生产者,在同步初始化连接的情况下将启动一个Connection的创建线程作为生产者。
我们打开生产者线程实现的run方法,代码主要是以下两块
第一部分,等待创建连接的信号
第二部分,创建完成的非空信号通知(通知等待创建完成的线程去消费Connection)
我们可以看到,在初始化过程中创建了相应数量的Connection连接,再启动了一个Connection生产者线程等待消费者通知去创建Connection连接。
init方法结束以后,我们再进入getConnectionDirect方法看看怎么获取连接
该方法又调用了一个内部方法,如果失败有重试机制,再进入内部方法
getConnectionInternal方法很长,我们忽略次要部分,关注以下内容
如果设置了等待时间则调用pollLast阻塞等待,如果没有则调用takeLast无限阻塞。这里我们点开pollLast()方法看看
首先,如果没有Connection就会发送空的信号给我们上面的生产者线程
然后就阻塞等待生产者的通知
等待结束以后再次判断是否为空(这里是典型的条件谓词的二次校验做法),还为空则再循环一遍。
如果有可用连接将获取并返回
原理实现
DruidDataSource的原理看起来比较简单,就是基于ReentrantLock和Condition的条件队列实现了生产者和消费者模式。生产者由一个线程处理(同步模式),消费者多线程。
下面我们看一个伪代码:
首先定义一把锁和两个信号,一个是空信号,一个是非空信号
生产者,先进行条件判断,如果非空则进入等待,如果空,则创建
消费者
伪代码中注意一点,wait方法在之前要做条件谓词判断,之后也要做条件谓词判断这样才不会导致信号丢失。