对于一些计算机上有限的资源(例如端口),或者创建/销毁的开销比较大的资源(例如线程,连接),通常会创建一个资源池,在需要使用资源时,由资源池分配,如果暂时没有可用的资源则需要等待。使用之后再将资源返还给资源池(如果还可以复用的话)。这样做可以提高资源利用效率,避免某一进程占用过多资源,也可以提高性能。
资源池一些常见的例子,包括数据库连接池、线程池、对象池等等。在实际应用中,通常需要在多线程环境下对资源池进行访问,因此资源池的需要做到线程安全。
正在进行的一个项目中的连接池实现:
https://github.com/duoertai/SagresMemcachedClient/blob/master/src/kyrat/sagres/ConnectionPool.java
大体思路类似,所以由连接池推广到需要线程安全的各类资源池。
- 资源池可以在初始化时创建一定数量的资源,当然这是可选的。
- 资源池要确定最大资源数目,总体资源数目不能超过这个数值。
- 资源池要记录现在可用的资源数。
- 资源池要记录当前总体资源数目,即资源池内可用资源数+交由请求者使用的资源数
向资源池请求资源时
- 如果有可用的资源则交给请求者
- 如果没有可用资源,如果总体资源数小于最大资源数,则创建新的资源并交由请求者使用,否则需要请求者等待
在多线程访问的环境下,需要对线程池的一些操作在必要的步骤加锁进行同步,然而如果在不要的步骤加了锁,则会导致性能降低。
在请求者请求资源时先lock,修改可用资源数,尝试获取资源,再unlock,如果没有得到可用资源,则根据上述原则创建新的资源,创建新资源部分,因为还没加入池中,不属于边界资源,这一步不需要加锁同步。只要在初始化资源池时状态是一致的,之后多线程获取资源的情况下也是一致的。
在请求者释放资源时,则需要先lock,将资源放回池中,修改可用资源数,unlock。即整个释放的过程都需要进行同步。这样才可以保证资源池状态一致性。否则可能出现一个线程释放资源时,修改了可用资源数,却没有将资源释放,然后被挂起,另一线程则发现还有可用的资源,然而却无法获取,从而创建了新的资源,造成数据不一致。
用一个日常生活中的例子打个比方:例如图书馆向外借书,《Java编程思想》有10本,最多可借出20本,如果不够则图书馆会额外订购,图书馆账目上记录了总共10本书,最初向外借出0本,还剩10本可借。每次借书时,需要同时完成修改账目和拿走一本书的操作,如果图书馆已经没有此书了,且总共数量不足20本,图书馆会告诉借书者已经订购了一本新的,此时借书者不需要继续占据柜台,可以让下一个借书者来借,当前借书者只需要等待图书馆给他购买的新书就可以。在借书者还书的时候,则需要完成修改账目和还书操作,才可以保证数据一致性,否则可能导致账目错误,造成额外订购了新书,从而使总数量超过20本。