如果请求流程,可以不看细节直接跳到总结
一、面试
1、连接池作用:估计都知道,保存数据库连接,避免下次请求时再新建耗时。
2、连接池存什么? 平常可能不太注意,仔细翻翻源码稍微思考一下就知道了。Mysql连接是mysql-connector-java.jar提供的,他给我返回Connection实例。而连接池存的就是Connection实例,高效的提供给多线程使用。
3、那到底跟我们自己List存一下有什么区别呢?也就是连接池解决什么问题、如何高效:多线程时,每个线程使用一个Connection,可能有100个线程同时getConnection,我们只有10个,那就需要让有的线程等待。这就是要解决的并发问题。至于如何高效,emmm…HikariCP优化了很多细节,这里不研究。我今天只说说它的主要流程,也是自己看过后的总结,希望帮助大家入门。
4、Hikari连接池高性能的原因?
1、采用自定义的FastList替代了ArrayList,FastList的get方法去除了范围检查rangeCheck逻辑,并且remove方法是从尾部开始扫描的,而并不是从头部开始扫描的。因为Connection的打开和关闭顺序通常是相反的
2、初始化时创建了两个HikariPool对象,一个采用final类型定义,避免在获取连接时才初始化,因为获取连接时才初始化就需要做同步处理
3、Hikari创建连接是通过javassist动态字节码生成技术创建的,性能更好
4、从连接池中获取连接时对于同一个线程在threadLocal中添加了缓存,同一线程获取连接时没有并发操作
5、Hikari最大的特点是在高并发的情况下尽量的减少锁竞争
二、主要类
1、HikariDataSource:就是一个DataSource,包含HikariPoo成员变量
2、HikariPool:HikariCP的入口,包含了ConcurrentBag成员变量
3、ConcurrentBag:用于高效处理并发的类:
包含了ThreadLocal<List> threadList、CopyOnWriteArrayList sharedList、SynchronousQueue、handoffQueue。这三个变量就是核心,他们是储存Connection的地方。
4、PoolEntry:封装储存Connection的类,可以理解一个PoolEntry就是一个Connection。
【如何管理的】
ConcurrentBag里面的那三个变量,可以从这三个地方获取到Connection。
ThreadLocal threadList 可以称之为“第一选择”。
一个线程获取到Connection用完后,先存在ThreadLocal,下次就可以直接先用了,如果去sharedList里拿,还要对waiter这个AotmicInteger进行CAS操作,直接拿更快。
CopyOnWriteArrayList sharedList 可以称之为“第二选择”。
所有创建的Connection都储存在这个list中。
SynchronousQueue handoffQueue 可以称之为“最后的选择”。
如果以上两个地方都拿不到PoolEntry,那就来这个队列等着。等到天荒地老,直到timeout超时。
三、流程讲解
1、不管哪个线程池,获取Connection接口都是DataSource.getConnection()
2、之后会调用到HikariCP的核心ConcurrentBag.borrow方法。开始了它的选择之路,也就是我说的三种选择。
3、第一选择:ThreadLoacl。第一次肯定是没有的,如果之前用过,那第二次就可以直接用。不过用之前要先看看PoolEntry的状态,因为,别的线程可能已经通过sharedList“偷”走了它。
4、第二选择:SharedList。遍历看看PoolEntry的状态,如果有未使用的,那就拿过来用。因为可能会偷了别家线程的,所以要给Listener发个消息,通知创建一个add任务新建Connection。
5、第三选择,最后的倔强:handoffQueue。如果现有的PoolEntry都不可用,那就要执行一次add任务了,跟上面的方法相同。通知完,就doWhile起来,在那等,add任务创建的PoolEntry或者其他方法归还的PoolEntry都会放到队列中。如果等不到,那timeout了就抛异常了。
6、归还。ProxyConnection.close()方法,补充一下ProxyConnection是对PoolEntry的包装。偶买噶。最终会调用bag的requite[翻译为“报答、归还”吧]方法。改方法有两个去向,当waiter>0时,就给队列,否则就放到ThreadLoacl里。