池化技术(JAVA)分析

栏目: Java · 发布时间: 6年前

内容简介:池化技术能够减少资源对象的创建次数,提高程序的响应性能,特别是在高并发下这种提高更加明显。使用池化技术缓存的资源对象有如下共同特点:1,对象创建时间长;2,对象创建需要大量资源;3,对象创建后可被重复使用。下面介绍的thread,connection等对象都具有上面的几个共同特点。本文通过jdk1.8的threadPool、jedis-client使用的apache-commons-pool2[2.4.2]、以及数据库连接池druid[1.1.10]等场景分析来感受下池化技术的使用。一个资源池具备如下功能

简介

池化技术能够减少资源对象的创建次数,提高程序的响应性能,特别是在高并发下这种提高更加明显。使用池化技术缓存的资源对象有如下共同特点:1,对象创建时间长;2,对象创建需要大量资源;3,对象创建后可被重复使用。下面介绍的thread,connection等对象都具有上面的几个共同特点。本文通过jdk1.8的threadPool、jedis-client使用的apache-commons-pool2[2.4.2]、以及数据库连接池druid[1.1.10]等场景分析来感受下池化技术的使用。

一个资源池具备如下功能:租用资源对象、归还资源对象、清除过期资源对象,接下来我们就从这几个功能点出发分别进行分析。

一 jdk1.8的threadPool

线程池对外提供了一个任务提交入口 execute(Runnable command) ,这个接口接收任务并在内部使用线程池来执行。我们提交多个任务的时候,线程池使用多个线程同时执行我们的任务,下面主要分析线程池线程之间是如何组织来执行我们的任务的。

首先分析下ThreadPoolExecutor的核心属性和方法核心属性:

  • threadFactory ---负责创建新的线程
  • works ---保存当前所有的Worker(对thread的包装),类型为HashSet
  • workQueue ---(当前的corePoolSize达到的时候,新提交的任务保存在这里)
  • corePoolSize ---核心Worker数量
  • maxPoolSize --- 最大的Worker数量

在execute内部有如下几个主流程

当前worker数量小于corePoolSize的时候创建新的线程并用这个线程执行提交的任务

简化代码:
1 addWorker(command, true)  
2 new Worker(firstTask) ;  
3 workers.add(w);  
4 t = w.thread;  
5 t.start();

当前worker数量大于等于corePoolSize的时候把任务添加到workQueue

workQueue.offer(command)

当前worker数量超过了workQueue的capacity的时候创建新的线程并用这个线程执行提交的任务

这里注意addWorker的第二个参数为false
1 addWorker(command, false)  
内部使用逻辑:
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false;  
2 new Worker(firstTask) ;  
3 workers.add(w);  
4 t = w.thread;  
5 t.start();

当前worker数量大于maxPoolSize的时候执行拒绝策略:

reject(command);

线程重复利用以及回收

主要是在Work类里面做的

Worker核类心属性及方法:

  • Thread thread --- 用于执行任务的线程
  • Runnable firstTask --- 提交时候的任务
  • Worker(Runnable firstTask) --- 创建一个Worker
  • void run() --- 启动thread执行任务
通过构造方法调用threadFactory创建新的线程
Worker(Runnable firstTask) {  
      setState(-1); // inhibit interrupts until runWorker
      this.firstTask = firstTask;
      this.thread = getThreadFactory().newThread(this);
}

run()代码片段

runWorker(this)  
1 while (task != null || (task = getTask()) != null) {  
2 beforeExecute(wt, task);  
3 task.run();  
4 afterExecute(task, thrown);  线程执行抛出的一些异常处理  
}
5 processWorkerExit(w, completedAbruptly); 从works移除work

代码1循环结束条件是没有可执行的任务即当getTask()==null的时候,这时当前线程的执行也就结束了,等同于这个线程的回收;同时资源的重复利用点在于这里的循环。代码5主要是执行了workers.remove(w)移除操作。为了保证池内至少存在corePoolSize的work,在getTask()内部判断当前workCount的数量,如果小于了coreSize,那么当前线程会一直block,直到新的task到来。如果当前workcount的数量超过了corePoolSize,并且workQueue为空,表示这个线程不需要了,最多等待keepAliveTime的时间,也就是超过corePoolSize的线程最长存活的时间。

getTask()代码片段如下:

boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;  
Runnable r = timed ?  
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();

小结

从上面的流程可以发现线程池没有显示的调用归还线程,因为线程是一直处于待执行状态,只要有任务到来就立即执行;对于线程的清理,就是始终判断当前线程数量是否满足预设的线程池数量,如果超过就不再接收新的任务自然就退出了。

二 commons-pool2

commons-pool2在很多客户端被用于资源池的实现,jedis-client就是其中之一。commons-pool2和上面的threadPool不同,commons-pool2内部的资源对象有不同的状态,使用中、空闲等状态,并且只有空闲状态的资源对象是可以被申请使用的。

下面主要分析commons-pool2是如何提供池的功能的。

GenericObjectPool核心类属性及方法:

  • PooledObjectFactory factory --- 用于创建新对象的工厂接口
  • LinkedBlockingDeque > idleObjects --- 保存空闲资源的双端队列
  • T borrowObject() ---租用对象
  • returnObject(T obj) ---归还对象

租用对象过程

1 T borrowObject() -> idleObjects.pollFirst()  
2 if(p == null) p = create() -> factory.makeObject()

归还对象过程

void returnObject(T obj) -> idleObjects.addLast(p);  
这个方法一般不是直接被调用的,而是框架(spring-data-redis)每次使用完了jedis连接后会调用jedis.close方法,这个方法进一步调用returnObject。

清除过期对象

BaseGenericObjectPool.Evictor类:  
run() -> evict() -> destroy(underTest) -> idleObjects.remove(toDestory)  
在设置timeBetweenEvictionRunsMillis的时候会开启这个定时任务。

在jedis-client的使用代码片段:

内部维护一个GenericObjectPool对象来实现 redis 连接的复用
redis.clients.util.Pool类  
public void initPool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory) {  
    if (this.internalPool != null) {
      try {
        closeInternalPool();
      } catch (Exception e) {
      }
    }
    this.internalPool = new GenericObjectPool<T>(factory, poolConfig);
  }

小结

commons-pool2的结构比较清晰,为资源对象的存储回收都提供了很好的api,在一般的项目中接入使用很容易同时也非常高效。

三 druid

druid是一个数据库连接池,池内维护的是数据库的连接。druid并没有使用commons-pool2这个框架,而是自己通过数组的方式实现了池的所有功能。下面从druid创建连接、租用连接以及回收连接的过程分析池的所有功能。

DruidDataSource核心类属性及方法:

  • DruidConnectionHolder[] connections ---保存空闲的连接
  • int poolingCount ---当前池内资源对象的计算
  • DruidConnectionHolder[] evictConnections ---要被移除的连接
  • ReentrantLock lock --- 重入锁保证connections数组的安全访问
  • Condition notEmpty --- 空闲连接全部被使用等待其他客户释放链接,当poolingCount为0的时候await,归还连接的时候signal。
  • Condition empty --- 创建连接的线程里面控制,当poolingCount为0的时候signal创建连接,当前active的连接超过maxActive进行await。
  • DruidPooledConnection getConnectionInternal(long maxWait) --- 获取一个空闲连接
  • recycle(DruidPooledConnection pooledConnection) --- 回收连接

创建连接

DruidDataSource:  
init -> CreateConnectionThread.run -> createPhysicalConnection

从数组获取一个空闲连接

DruidDataSource:  
getConnection(long maxWaitMillis) -> getConnectionInternal(maxWaitMillis) -> pollLast(nanos) -> DruidConnectionHolder last = connections[poolingCount]  
这里可以看到每次都是获取数组的最后一个元素

归还连接

DruidPooledConnection:  
DruidPooledConnection 实现javax.sql.PooledConnection;这个方法在框架(mybatis)里面会执行 sql 操作然后在finally代码块执行javax.sql.PooledConnection.close()  
close() ->  recycle() -> dataSource.recycle(this) -> putLast(holder, lastActiveTimeMillis) -> connections[poolingCount] = e

关闭过期的连接

DruidDataSource:  
在shrink方法内部会判断idleTime是否满足条件
init -> createAndStartDestroyThread() -> run -> shrink(true, keepAlive) -> evictConnections[evictCount++] = connection -> close()  
注意这里的close和上面归还连接的close是不同的,这里是物理关闭

小结

druid没有像commons-pool2一样使用双端队列,而是使用了数组;也没有像commons-pool2一样为对象设置多个不同的状态,druid使用两个数组,一个用于存储当前可以使用的空闲连接,一个用于存储要被清理的连接。由于druid没有使用双端队列,在并发不是很高的情况,数组里面最后一个连接被使用的频率最高,极端情况只使用这一个,当然这种情况对数据库应用没有影响。

四 总结

从上面的三个组件可以看出资源对象的存储使用了集合、双端队列、数组等数据结构;在面对资源竞争时使用锁和Condition的组合来提高资源获取的效率;当资源不够时,为客户获取资源对象提供了快速失败、等待指定时间再失败,无限等待等方式。最后本文并没有分析一些技术细节,这不是本文的重点。同时本文选择的这几个组件进行池化对比分析并不是最好,比如分析数据库连接池可以把druid、DBCP、Tomcat-jdbc、C3P0来进行对比分析会更好,这是本文后续需要补充的,之所以分析上面的三个组件是因为目前项目里面这几个用的比较多。


以上所述就是小编给大家介绍的《池化技术(JAVA)分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

XMPP

XMPP

Peter Saint-Andre、Kevin Smith、Remko TronCon / O'Reilly Media / 2009-5-4 / USD 39.99

This practical book provides everything you need to know about the Extensible Messaging and Presence Protocol (XMPP). This open technology for real-time communication is used in many diverse applicati......一起来看看 《XMPP》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具