詳解commons-pool2池化技術(shù)
一、前言
我們經(jīng)常會(huì)接觸各種池化的技術(shù)或者概念,包括對(duì)象池、連接池、線程池等,池化技術(shù)最大的好處就是實(shí)現(xiàn)對(duì)象的重復(fù)利用,尤其是創(chuàng)建和使用大對(duì)象或者寶貴資源(HTTP連接對(duì)象,MySQL連接對(duì)象)等方面的時(shí)候能夠大大節(jié)省系統(tǒng)開銷,對(duì)提升系統(tǒng)整體性能也至關(guān)重要。
在并發(fā)請(qǐng)求下,如果需要同時(shí)為幾百個(gè)query操作創(chuàng)建/關(guān)閉MySQL的連接或者是為每一個(gè)HTTP請(qǐng)求創(chuàng)建一個(gè)處理線程或者是為每一個(gè)圖片或者XML解析創(chuàng)建一個(gè)解析對(duì)象而不使用池化技術(shù),將會(huì)給系統(tǒng)帶來極大的負(fù)載挑戰(zhàn)。
二、commons-pool2池化技術(shù)剖析
越來越多的框架在選擇使用apache commons-pool2進(jìn)行池化的管理,如jedis-cluster,commons-pool2工作的邏輯如下圖所示:
2.1、核心三元素
2.1.1、ObjectPool
對(duì)象池,負(fù)責(zé)對(duì)對(duì)象進(jìn)行生命周期的管理,并提供了對(duì)對(duì)象池中活躍對(duì)象和空閑對(duì)象統(tǒng)計(jì)的功能。
2.1.2、PooledObjectFactory
對(duì)象工廠類,負(fù)責(zé)具體對(duì)象的創(chuàng)建、初始化,對(duì)象狀態(tài)的銷毀和驗(yàn)證。commons-pool2框架本身提供了默認(rèn)的抽象實(shí)現(xiàn)BasePooledObjectFactory ,業(yè)務(wù)方在使用的時(shí)候只需要繼承該類,然后實(shí)現(xiàn)warp和create方法即可。
2.1.3、PooledObject
池化對(duì)象,是需要放到ObjectPool對(duì)象的一個(gè)包裝類。添加了一些附加的信息,比如說狀態(tài)信息,創(chuàng)建時(shí)間,激活時(shí)間等。commons-pool2提供了DefaultPooledObject和 PoolSoftedObject 2種實(shí)現(xiàn)。其中PoolSoftedObject繼承自DefaultPooledObject,不同點(diǎn)是使用SoftReference實(shí)現(xiàn)了對(duì)象的軟引用。獲取對(duì)象的時(shí)候使用也是通過SoftReference進(jìn)行獲取。
2.2、對(duì)象池邏輯分析
2.2.1、對(duì)象池接口說明
1)我們?cè)谑褂胏ommons-pool2的時(shí)候,應(yīng)用程序獲取或釋放對(duì)象的操作都是基于對(duì)象池進(jìn)行的,對(duì)象池核心接口主要包括如下:
/** *向?qū)ο蟪刂性黾訉?duì)象實(shí)例 */ void addObject() throws Exception, IllegalStateException, UnsupportedOperationException; /** * 從對(duì)象池中獲取對(duì)象 */ T borrowObject() throws Exception, NoSuchElementException, IllegalStateException; /** * 失效非法的對(duì)象 */ void invalidateObject(T obj) throws Exception; /** * 釋放對(duì)象至對(duì)象池 */ void returnObject(T obj) throws Exception;
除了接口本身之外,對(duì)象池還支持對(duì)對(duì)象的最大數(shù)量,保留時(shí)間等等進(jìn)行設(shè)置。對(duì)象池的核心參數(shù)項(xiàng)包括maxTotal,maxIdle,minIdle,maxWaitMillis,testOnBorrow 等。
2.2.2、對(duì)象創(chuàng)建解耦
對(duì)象工廠是commons-pool2框架中用于生成對(duì)象的核心環(huán)節(jié),業(yè)務(wù)方在使用過程中需要自己去實(shí)現(xiàn)對(duì)應(yīng)的對(duì)象工廠實(shí)現(xiàn)類,通過工廠模式,實(shí)現(xiàn)了對(duì)象池與對(duì)象的生成與實(shí)現(xiàn)過程細(xì)節(jié)的解耦,每一個(gè)對(duì)象池應(yīng)該都有對(duì)象工廠的成員變量,如此實(shí)現(xiàn)對(duì)象池本身和對(duì)象的生成邏輯解耦。
可以通過代碼進(jìn)一步驗(yàn)證我們的思路:
public GenericObjectPool(final PooledObjectFactory<T> factory) { this(factory, new GenericObjectPoolConfig<T>()); } public GenericObjectPool(final PooledObjectFactory<T> factory, final GenericObjectPoolConfig<T> config) { super(config, ONAME_BASE, config.getJmxNamePrefix()); if (factory == null) { jmxUnregister(); // tidy up throw new IllegalArgumentException("factory may not be null"); } this.factory = factory; idleObjects = new LinkedBlockingDeque<>(config.getFairness()); setConfig(config); } public GenericObjectPool(final PooledObjectFactory<T> factory, final GenericObjectPoolConfig<T> config, final AbandonedConfig abandonedConfig) { this(factory, config); setAbandonedConfig(abandonedConfig); }
可以看到對(duì)象池的構(gòu)造方法,都依賴于對(duì)象構(gòu)造工廠PooledObjectFactory,在生成對(duì)象的時(shí)候,基于對(duì)象池中定義的參數(shù)和對(duì)象構(gòu)造工廠來生成。
/** * 向?qū)ο蟪刂性黾訉?duì)象,一般在預(yù)加載的時(shí)候會(huì)使用該功能 */ @Override public void addObject() throws Exception { assertOpen(); if (factory == null) { throw new IllegalStateException( "Cannot add objects without a factory."); } final PooledObject<T> p = create(); addIdleObject(p); }
create() 方法基于對(duì)象工廠來生成的對(duì)象,繼續(xù)往下跟進(jìn)代碼來確認(rèn)邏輯;
final PooledObject<T> p; try { p = factory.makeObject(); if (getTestOnCreate() && !factory.validateObject(p)) { createCount.decrementAndGet(); return null; } } catch (final Throwable e) { createCount.decrementAndGet(); throw e; } finally { synchronized (makeObjectCountLock) { makeObjectCount--; makeObjectCountLock.notifyAll(); } }
此處確認(rèn)了factory.makeObject()的操作,也印證了上述的推測(cè),基于對(duì)象工廠來生成對(duì)應(yīng)的對(duì)象。
為了更好的能夠?qū)崿F(xiàn)對(duì)象池中對(duì)象的使用以及跟蹤對(duì)象的狀態(tài),commons-pool2框架中使用了池化對(duì)象PooledObject的概念,PooledObject本身是泛型類,并提供了getObject()獲取實(shí)際對(duì)象的方法。
2.2.3、對(duì)象池源碼分析
經(jīng)過上述分析我們知道了對(duì)象池承載了對(duì)象的生命周期的管理,包括整個(gè)對(duì)象池中對(duì)象數(shù)量的控制等邏輯,接下來我們通過GenericObjectPool的源碼來分析究竟是如何實(shí)現(xiàn)的。
對(duì)象池中使用了雙端隊(duì)列LinkedBlockingDeque來存儲(chǔ)對(duì)象,LinkedBlockingDeque對(duì)列支持FIFO和FILO兩種策略,基于AQS來實(shí)現(xiàn)隊(duì)列的操作的協(xié)同。
LinkedBlockingDeque提供了隊(duì)尾和隊(duì)頭的插入和移除元素的操作,相關(guān)操作都進(jìn)行了加入重入鎖的加鎖操作隊(duì)列中設(shè)置notFull 和 notEmpty兩個(gè)狀態(tài)變量,當(dāng)對(duì)隊(duì)列進(jìn)行元素的操作的時(shí)候會(huì)觸發(fā)對(duì)應(yīng)的執(zhí)行await和notify等操作。
/** * 第一個(gè)節(jié)點(diǎn) * Invariant: (first == null && last == null) || * (first.prev == null && first.item != null) */ private transient Node<E> first; // @GuardedBy("lock") /** * 最后一個(gè)節(jié)點(diǎn) * Invariant: (first == null && last == null) || * (last.next == null && last.item != null) */ private transient Node<E> last; // @GuardedBy("lock") /** 當(dāng)前隊(duì)列長(zhǎng)度 */ private transient int count; // @GuardedBy("lock") /** 隊(duì)列最大容量 */ private final int capacity; /** 主鎖 */ private final InterruptibleReentrantLock lock; /** 隊(duì)列是否為空狀態(tài)鎖 */ private final Condition notEmpty; /** 隊(duì)列是否滿狀態(tài)鎖 */ private final Condition notFull;
隊(duì)列核心點(diǎn)為:
1.隊(duì)列中所有的移入元素、移出、初始化構(gòu)造元素都是基于主鎖進(jìn)行加鎖操作。
2.隊(duì)列的offer和pull支持設(shè)置超時(shí)時(shí)間參數(shù),主要是通過兩個(gè)狀態(tài)Condition來進(jìn)行協(xié)調(diào)操作。如在進(jìn)行offer操作的時(shí)候,如果操作不成功,則基于notFull狀態(tài)對(duì)象進(jìn)行等待。
public boolean offerFirst(final E e, final long timeout, final TimeUnit unit) throws InterruptedException { Objects.requireNonNull(e, "e"); long nanos = unit.toNanos(timeout); lock.lockInterruptibly(); try { while (!linkFirst(e)) { if (nanos <= 0) { return false; } nanos = notFull.awaitNanos(nanos); } return true; } finally { lock.unlock(); } }
如進(jìn)行pull操作的時(shí)候,如果操作不成功,則對(duì)notEmpty進(jìn)行等待操作。
public E takeFirst() throws InterruptedException { lock.lock(); try { E x; while ( (x = unlinkFirst()) == null) { notEmpty.await(); } return x; } finally { lock.unlock(); } }
反之當(dāng)操作成功的時(shí)候,則進(jìn)行喚醒操作,如下所示:
private boolean linkLast(final E e) { // assert lock.isHeldByCurrentThread(); if (count >= capacity) { return false; } final Node<E> l = last; final Node<E> x = new Node<>(e, l, null); last = x; if (first == null) { first = x; } else { l.next = x; } ++count; notEmpty.signal(); return true; }
2.3、核心業(yè)務(wù)流程
2.3.1、池化對(duì)象狀態(tài)變更
上圖是PooledObject的狀態(tài)機(jī)圖,藍(lán)色表示狀態(tài),紅色表示與ObjectPool相關(guān)的方法.PooledObject的狀態(tài)為:IDLE、ALLOCATED、RETURNING、ABANDONED、INVALID、EVICTION、EVICTION_RETURN_TO_HEAD
所有狀態(tài)是在PooledObjectState類中定義的,其中一些是暫時(shí)未使用的,此處不再贅述。
2.3.2、對(duì)象池browObject過程
第一步、根據(jù)配置確定是否要為標(biāo)簽刪除調(diào)用removeAbandoned方法。
第二步、嘗試獲取或創(chuàng)建一個(gè)對(duì)象,源碼過程如下:
//1、嘗試從雙端隊(duì)列中獲取對(duì)象,pollFirst方法是非阻塞方法 p = idleObjects.pollFirst(); if (p == null) { p = create(); if (p != null) { create = true; } } if (blockWhenExhausted) { if (p == null) { if (borrowMaxWaitMillis < 0) { //2、沒有設(shè)置最大阻塞等待時(shí)間,則無限等待 p = idleObjects.takeFirst(); } else { //3、設(shè)置最大等待時(shí)間了,則阻塞等待指定的時(shí)間 p = idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS); } } }
示意圖如下所示:
第三步、調(diào)用allocate使?fàn)顟B(tài)更改為ALLOCATED狀態(tài)。
第四步、調(diào)用工廠的activateObject來初始化對(duì)象,如果發(fā)生錯(cuò)誤,請(qǐng)調(diào)用destroy方法來銷毀對(duì)象,例如源代碼中的六個(gè)步驟。
第五步、調(diào)用TestFactory的validateObject進(jìn)行基于TestOnBorrow配置的對(duì)象可用性分析,如果不可用,則調(diào)用destroy方法銷毀對(duì)象。3-7步驟的源碼過程如下所示:
//修改對(duì)象狀態(tài) if (!p.allocate()) { p = null; } if (p != null) { try { //初始化對(duì)象 factory.activateObject(p); } catch (final Exception e) { try { destroy(p, DestroyMode.NORMAL); } catch (final Exception e1) { } } if (p != null && getTestOnBorrow()) { boolean validate = false; Throwable validationThrowable = null; try { //驗(yàn)證對(duì)象的可用性狀態(tài) validate = factory.validateObject(p); } catch (final Throwable t) { PoolUtils.checkRethrow(t); validationThrowable = t; } //對(duì)象不可用,驗(yàn)證失敗,則進(jìn)行destroy if (!validate) { try { destroy(p, DestroyMode.NORMAL); destroyedByBorrowValidationCount.incrementAndGet(); } catch (final Exception e) { // Ignore - validation failure is more important } } } }
2.3.3、對(duì)象池returnObject的過程執(zhí)行邏輯
第一步、調(diào)用markReturningState方法將狀態(tài)更改為RETURNING。
第二步、基于testOnReturn配置調(diào)用PooledObjectFactory的validateObject方法以進(jìn)行可用性檢查。如果檢查失敗,則調(diào)用destroy消耗該對(duì)象,然后確保調(diào)用idle以確保池中有IDLE狀態(tài)對(duì)象可用,如果沒有,則調(diào)用create方法創(chuàng)建一個(gè)新對(duì)象。
第三步、調(diào)用PooledObjectFactory的passivateObject方法進(jìn)行反初始化操作。
第四步、調(diào)用deallocate將狀態(tài)更改為IDLE。
第五步、檢測(cè)是否已超過最大空閑對(duì)象數(shù),如果超過,則銷毀當(dāng)前對(duì)象。
第六步、根據(jù)LIFO(后進(jìn)先出)配置將對(duì)象放置在隊(duì)列的開頭或結(jié)尾。
2.4、拓展和思考
2.4.1、關(guān)于LinkedBlockingDeque的另種實(shí)現(xiàn)
上文中分析到commons-pool2中使用了雙端隊(duì)列以及java中的condition來實(shí)現(xiàn)隊(duì)列中對(duì)象的管理和不同線程對(duì)對(duì)象獲取和釋放對(duì)象操作之間的協(xié)調(diào),那是否有其他方案可以實(shí)現(xiàn)類似效果呢?答案是肯定的。
使用雙端隊(duì)列進(jìn)行操作,其實(shí)是想將空閑對(duì)象和活躍對(duì)象進(jìn)行隔離,本質(zhì)上將我們用兩個(gè)隊(duì)列來分別存儲(chǔ)空閑隊(duì)列和當(dāng)前活躍對(duì)象,然后再統(tǒng)一使用一個(gè)對(duì)象鎖,也是可以達(dá)成相同的目標(biāo)的,大概的思路如下:
1、雙端隊(duì)列改為兩個(gè)單向隊(duì)列分別用于存儲(chǔ)空閑的和活躍的對(duì)象,隊(duì)列之間的同步和協(xié)調(diào)可以通過對(duì)象鎖的wait和notify完成。
public class PoolState { protected final List<PooledObject> idleObjects = new ArrayList<>(); protected final List<PooledObject> activeObjects = new ArrayList<>(); //... }
2、在獲取對(duì)象時(shí)候,原本對(duì)雙端隊(duì)列的LIFO或者FIFO變成了從空閑隊(duì)列idleObjects中獲取對(duì)象,然后在獲取成功并對(duì)象狀態(tài)合法后,將對(duì)象添加到活躍對(duì)象集合activeObjects 中,如果獲取對(duì)象需要等待,則PoolState對(duì)象鎖應(yīng)該通過wait操作,進(jìn)入等待狀態(tài)。
3、在釋放對(duì)象的時(shí)候,則首先從活躍對(duì)象集合activeObjects 刪除元素,刪除完成后,將對(duì)象增加到空閑對(duì)象集合idleObjects中,需要注意的是,在釋放對(duì)象過程中也需要去校驗(yàn)對(duì)象的狀態(tài)。當(dāng)對(duì)象狀態(tài)不合法的時(shí)候,對(duì)象應(yīng)該進(jìn)行銷毀,不應(yīng)該添加到idleObjects中。釋放成功后則PoolState通過notify或者notifyAll喚醒等待中的獲取操作。
4、為保障對(duì)活躍隊(duì)列和空閑隊(duì)列的操作線程安全性,獲取對(duì)象和釋放對(duì)象需要進(jìn)行加鎖操作,和commons2-pool中的一致。
2.4.2、對(duì)象池的自我保護(hù)機(jī)制
我們?cè)谑褂胏ommons-pool2中獲取對(duì)象的時(shí)候,會(huì)從雙端隊(duì)列中阻塞等待獲取元素(或者是創(chuàng)建新對(duì)象),但是如果是應(yīng)用程序的異常,一直未調(diào)用returnObject或者invalidObject的時(shí)候,那可能就會(huì)出現(xiàn)對(duì)象池中的對(duì)象一直上升,到達(dá)設(shè)置的上線之后再去調(diào)用borrowObject的時(shí)候就會(huì)出現(xiàn)一直等待或者是等待超時(shí)而無法獲取對(duì)象的情況。
commons-pool2為了避免上述分析的問題的出現(xiàn),提供了兩種自我保護(hù)機(jī)制:
基于閾值的檢測(cè):
從對(duì)象池中獲取對(duì)象的時(shí)候會(huì)校驗(yàn)當(dāng)前對(duì)象池的活躍對(duì)象和空閑對(duì)象的數(shù)量占比,當(dāng)空閑獨(dú)享非常少,活躍對(duì)象非常多的時(shí)候,會(huì)觸發(fā)空閑對(duì)象的回收,具體校驗(yàn)規(guī)則為:如果當(dāng)前對(duì)象池中少于2個(gè)idle狀態(tài)的對(duì)象或者 active數(shù)量>最大對(duì)象數(shù)-3 的時(shí)候,在borrow對(duì)象的時(shí)候啟動(dòng)泄漏清理。通過AbandonedConfig.setRemoveAbandonedOnBorrow 為 true 進(jìn)行開啟。
//根據(jù)配置確定是否要為標(biāo)簽刪除調(diào)用removeAbandoned方法 final AbandonedConfig ac = this.abandonedConfig; if (ac != null && ac.getRemoveAbandonedOnBorrow() && (getNumIdle() < 2) && (getNumActive() > getMaxTotal() - 3) ) { removeAbandoned(ac); }
異步調(diào)度線程檢測(cè):
AbandonedConfig.setRemoveAbandonedOnMaintenance 設(shè)置為 true 以后,在維護(hù)任務(wù)運(yùn)行的時(shí)候會(huì)進(jìn)行泄漏對(duì)象的清理,通過設(shè)置setTimeBetweenEvictionRunsMillis 來設(shè)置維護(hù)任務(wù)執(zhí)行的時(shí)間間隔。
檢測(cè)和回收實(shí)現(xiàn)邏輯分析:
在構(gòu)造方法內(nèi)部邏輯的最后調(diào)用了startEvictor方法。這個(gè)方法的作用是在構(gòu)造完對(duì)象池后,啟動(dòng)回收器來監(jiān)控回收空閑對(duì)象。startEvictor定義在GenericObjectPool的父類BaseGenericObjectPool(抽象)類中,我們先看一下這個(gè)方法的源碼。
在構(gòu)造器中會(huì)執(zhí)行如下的設(shè)置參數(shù);
public final void setTimeBetweenEvictionRunsMillis( final long timeBetweenEvictionRunsMillis) { this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; startEvictor(timeBetweenEvictionRunsMillis); }
當(dāng)且僅當(dāng)設(shè)置了timeBetweenEvictionRunsMillis參數(shù)后才會(huì)開啟定時(shí)清理任務(wù)。
final void startEvictor(final long delay) { synchronized (evictionLock) { EvictionTimer.cancel(evictor, evictorShutdownTimeoutMillis, TimeUnit.MILLISECONDS); evictor = null; evictionIterator = null; //如果delay<=0則不會(huì)開啟定時(shí)清理任務(wù) if (delay > 0) { evictor = new Evictor(); EvictionTimer.schedule(evictor, delay, delay); } } }
繼續(xù)跟進(jìn)代碼可以發(fā)現(xiàn),調(diào)度器中設(shè)置的清理方法的實(shí)現(xiàn)邏輯實(shí)際在對(duì)象池中定義的,也就是由GenericObjectPool或者GenericKeyedObjectPool來實(shí)現(xiàn),接下來我們繼續(xù)探究對(duì)象池是如何進(jìn)行對(duì)象回收的。
a)、核心參數(shù):
minEvictableIdleTimeMillis:指定空閑對(duì)象最大保留時(shí)間,超過此時(shí)間的會(huì)被回收。不配置則不過期回收。
softMinEvictableIdleTimeMillis:一個(gè)毫秒數(shù)值,用來指定在空閑對(duì)象數(shù)量超過minIdle設(shè)置,且某個(gè)空閑對(duì)象超過這個(gè)空閑時(shí)間的才可以會(huì)被回收。
minIdle:對(duì)象池里要保留的最小空間對(duì)象數(shù)量。
b)、回收邏輯
以及一個(gè)對(duì)象回收策略接口EvictionPolicy,可以預(yù)料到對(duì)象池的回收會(huì)和上述的參數(shù)項(xiàng)及接口EvictionPolicy發(fā)生關(guān)聯(lián),繼續(xù)跟進(jìn)代碼會(huì)發(fā)現(xiàn)如下的內(nèi)容,可以看到在判斷對(duì)象池可以進(jìn)行回收的時(shí)候,直接調(diào)用了destroy進(jìn)行回收。
boolean evict; try { evict = evictionPolicy.evict(evictionConfig, underTest, idleObjects.size()); } catch (final Throwable t) { // Slightly convoluted as SwallowedExceptionListener // uses Exception rather than Throwable PoolUtils.checkRethrow(t); swallowException(new Exception(t)); // Don't evict on error conditions evict = false; } if (evict) { // 如果可以被回收則直接調(diào)用destroy進(jìn)行回收 destroy(underTest); destroyedByEvictorCount.incrementAndGet(); }
為提升回收的效率,在回收策略判斷對(duì)象的狀態(tài)不是evict的時(shí)候,也會(huì)進(jìn)行進(jìn)一步的狀態(tài)判斷和處理,具體邏輯如下:
1.嘗試激活對(duì)象,如果激活失敗則認(rèn)為對(duì)象已經(jīng)不再存活,直接調(diào)用destroy進(jìn)行銷毀。
2.在激活對(duì)象成功的情況下,會(huì)通過validateObject方法取校驗(yàn)對(duì)象狀態(tài),如果校驗(yàn)失敗,則說明對(duì)象不可用,需要進(jìn)行銷毀。
boolean active = false; try { // 調(diào)用activateObject激活該空閑對(duì)象,本質(zhì)上不是為了激活, // 而是通過這個(gè)方法可以判定是否還存活,這一步里面可能會(huì)有一些資源的開辟行為。 factory.activateObject(underTest); active = true; } catch (final Exception e) { // 如果激活的時(shí)候,發(fā)生了異常,就說明該空閑對(duì)象已經(jīng)失聯(lián)了。 // 調(diào)用destroy方法銷毀underTest destroy(underTest); destroyedByEvictorCount.incrementAndGet(); } if (active) { // 再通過進(jìn)行validateObject校驗(yàn)有效性 if (!factory.validateObject(underTest)) { // 如果校驗(yàn)失敗,說明對(duì)象已經(jīng)不可用了 destroy(underTest); destroyedByEvictorCount.incrementAndGet(); } else { try { /* *因?yàn)樾r?yàn)還激活了空閑對(duì)象,分配了額外的資源,那么就通過passivateObject把在activateObject中開辟的資源釋放掉。 */ factory.passivateObject(underTest); } catch (final Exception e) { // 如果passivateObject失敗,也可以說明underTest這個(gè)空閑對(duì)象不可用了 destroy(underTest); destroyedByEvictorCount.incrementAndGet(); } } }
三、寫在最后
連接池能夠給程序開發(fā)者帶來一些便利性,前言中我們分析了使用池化技術(shù)的好處和必要性,但是我們也可以看到commons-pool2框架在對(duì)象的創(chuàng)建和獲取上都進(jìn)行了加鎖的操作,這會(huì)在并發(fā)場(chǎng)景下一定程度的影響應(yīng)用程序的性能,其次池化對(duì)象的對(duì)象池中對(duì)象的數(shù)量也是需要進(jìn)行合理的設(shè)置,否則也很難起到真正的使用對(duì)象池的目的,這給我們也帶來了一定的挑戰(zhàn)。
以上就是詳解commons-pool2池化技術(shù)的詳細(xì)內(nèi)容,更多關(guān)于commons-pool2池化技術(shù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Python 字符串池化的前提
- keras中的卷積層&池化層的用法
- 淺談pytorch池化maxpool2D注意事項(xiàng)
- TensorFlow tf.nn.max_pool實(shí)現(xiàn)池化操作方式
- PyTorch的自適應(yīng)池化Adaptive Pooling實(shí)例
- pytorch torch.nn.AdaptiveAvgPool2d()自適應(yīng)平均池化函數(shù)詳解
- 對(duì)Pytorch中Tensor的各種池化操作解析
- pytorch中的卷積和池化計(jì)算方式詳解
- 淺談tensorflow1.0 池化層(pooling)和全連接層(dense)
相關(guān)文章
java實(shí)現(xiàn)輸出字符串中第一個(gè)出現(xiàn)不重復(fù)的字符詳解
這篇文章主要介紹了java實(shí)現(xiàn)輸出字符串中第一個(gè)出現(xiàn)不重復(fù)的字符詳解的相關(guān)資料,需要的朋友可以參考下2017-04-04SpringBoot聲明式事務(wù)的簡(jiǎn)單運(yùn)用說明
這篇文章主要介紹了SpringBoot聲明式事務(wù)的簡(jiǎn)單運(yùn)用說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09mybatis二級(jí)緩存的實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了mybatis二級(jí)緩存的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10Spring Cloud Gateway + Nacos 實(shí)現(xiàn)動(dòng)態(tài)路由
這篇文章主要介紹了Spring Cloud Gateway + Nacos 實(shí)現(xiàn)動(dòng)態(tài)路由的方法,幫助大家實(shí)現(xiàn)路由信息的自動(dòng)更新,感興趣的朋友可以了解下2020-10-10淺談Java方法調(diào)用的優(yōu)先級(jí)問題
這篇文章主要介紹了淺談Java方法調(diào)用的優(yōu)先級(jí)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-10-10調(diào)用java.lang.Runtime.exec的正確姿勢(shì)分享
這篇文章主要介紹了調(diào)用java.lang.Runtime.exec的正確姿勢(shì),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11