内容简介:池化技术能够减少资源对象的创建次数,提高程序的响应性能,特别是在高并发下这种提高更加明显。使用池化技术缓存的资源对象有如下共同特点: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)分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript从入门到精通
明日科技 / 清华大学出版社 / 2012-9 / 69.80元
《JavaScript从入门到精通》从初学者角度出发,通过通俗易懂的语言、丰富多彩的实例,详细介绍了使用JavaScript语言进行程序开发应该掌握的各方面技术。全书共分24章,包括初识JavaScript、JavaScript基础、流程控制、函数、JavaScript对象与数组、字符串与数值处理对象、正则表达式、程序调试与错误处理、事件处理、处理文档(document对象)、文档对象模型(DOM......一起来看看 《JavaScript从入门到精通》 这本书的介绍吧!