欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java連接池Druid獲取連接getConnection示例詳解

 更新時(shí)間:2023年09月15日 11:51:53   作者:福  
這篇文章主要為大家介紹了java連接池Druid獲取連接getConnection示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

Druid連接池

Druid連接池只存儲(chǔ)在connections數(shù)組中,所以獲取連接的邏輯應(yīng)該比HikariPool簡單一些:直接從connectoins獲取即可。

DruidDataSource.getConnection

直接上代碼:

@Override
    public DruidPooledConnection getConnection() throws SQLException {
        return getConnection(maxWait);
    }

調(diào)用了getConnection(maxWait),maxWait是參數(shù)設(shè)定的獲取連接的最長等待時(shí)間,超過該時(shí)長還沒有獲取到連接的話,拋異常。

看getConnection(maxWait)代碼:

public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
        init();
        if (filters.size() > 0) {
            FilterChainImpl filterChain = new FilterChainImpl(this);
            return filterChain.dataSource_connect(this, maxWaitMillis);
        } else {
            return getConnectionDirect(maxWaitMillis);
        }
    }

先調(diào)用init,init方法會(huì)判斷連接池是否已經(jīng)完成了初始化,如果沒有完成初始化則首先進(jìn)行初始化,初始化的代碼我們上一篇文章已經(jīng)分析過了。

之后判斷是否有filters,filters的內(nèi)容我們先放放,暫時(shí)不管,直接看沒有filters的情況下,調(diào)用getConnectionDirect方法。

getConnectionDirect

方法比較長,我們還是老辦法,分段分析:

public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
        int notFullTimeoutRetryCnt = 0;
        for (;;) {
            // handle notFullTimeoutRetry
            DruidPooledConnection poolableConnection;
            try {
                poolableConnection = getConnectionInternal(maxWaitMillis);
            } catch (GetConnectionTimeoutException ex) {
                if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {
                    notFullTimeoutRetryCnt++;
                    if (LOG.isWarnEnabled()) {
                        LOG.warn("get connection timeout retry : " + notFullTimeoutRetryCnt);
                    }
                    continue;
                }
                throw ex;
            }

上來之后首先無限for循環(huán),目的是從連接池獲取到連接之后,根據(jù)參數(shù)設(shè)定可能會(huì)做必要的檢查,如果檢查不通過(比如連接不可用、連接已關(guān)閉等等)的話循環(huán)重新獲取。

然后調(diào)用getConnectionInternal獲取連接,getConnectionInternal方法應(yīng)該是我們今天文章的主角,我們稍微放一放,為了文章的可讀性,先分析完getConnectionDirect方法。

我們假設(shè)通過調(diào)用getConnectionInternal方法獲取到一個(gè)連接(注意獲取到的連接對(duì)象是DruidPooledConnection,不是Connection對(duì)象,這個(gè)也不難想象,連接池獲取到的連接一定是數(shù)據(jù)庫物理連接的代理對(duì)象(或者叫封裝對(duì)象,封裝了數(shù)據(jù)庫物理連接Connection對(duì)象的對(duì)象,這個(gè)原理我們?cè)诜治鯤ikariPool的時(shí)候已經(jīng)說過了。這個(gè)DruidPooledConnection對(duì)象我們也暫時(shí)放一放,后面分析)。

調(diào)用getConnectionInternal方法如果返回超時(shí)異常,判斷:如果當(dāng)前連接池沒滿,而且獲取連接超時(shí)重試次數(shù)小于參數(shù)notFullTimeoutRetryCount設(shè)定的次數(shù)的話,則continue,重新獲取連接。否則,拋出超時(shí)異常。

接下來:

if (testOnBorrow) {
                boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
                if (!validate) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("skip not validate connection.");
                    }
                    discardConnection(poolableConnection.holder);
                    continue;
                }
            } else {
                if (poolableConnection.conn.isClosed()) {
                    discardConnection(poolableConnection.holder); // 傳入null,避免重復(fù)關(guān)閉
                    continue;
                }

testOnBorrow參數(shù)的目的是:獲取連接后是否要做連接可用性測(cè)試,如果設(shè)定為true的話,調(diào)用testConnectionInternal測(cè)試連接的可用性,testConnectionInternal方法上一篇文章分析連接回收的時(shí)候、處理keepAlive的過程中就碰到過,就是執(zhí)行配置好的sql語句測(cè)試連接可用性,如果測(cè)試不通過的話則調(diào)用discardConnection關(guān)閉連接,continue重新獲取連接。

否則,如果testOnBorrow參數(shù)沒有打開的話,檢查當(dāng)前連接如果已經(jīng)關(guān)閉,則調(diào)用discardConnection關(guān)閉連接(沒太明白連接既然已經(jīng)是關(guān)閉狀態(tài),為啥還需要調(diào)用?),continue重新獲取連接。

不建議打開testOnBorrow參數(shù),因?yàn)檫B接池都會(huì)有連接回收機(jī)制,比如上一篇文章講過的Druid的DestroyConnectionThread & DestroyTask,回收參數(shù)配置正常的話,回收機(jī)制基本可以確保連接的可用性。打開testOnBorrow參數(shù)會(huì)導(dǎo)致每次獲取連接之后都測(cè)試連接的可用性,嚴(yán)重影響系統(tǒng)性能。

接下來:

if (testWhileIdle) {
                    final DruidConnectionHolder holder = poolableConnection.holder;
                    long currentTimeMillis             = System.currentTimeMillis();
                    long lastActiveTimeMillis          = holder.lastActiveTimeMillis;
                    long lastExecTimeMillis            = holder.lastExecTimeMillis;
                    long lastKeepTimeMillis            = holder.lastKeepTimeMillis;
                    if (checkExecuteTime
                            && lastExecTimeMillis != lastActiveTimeMillis) {
                        lastActiveTimeMillis = lastExecTimeMillis;
                    }
                    if (lastKeepTimeMillis > lastActiveTimeMillis) {
                        lastActiveTimeMillis = lastKeepTimeMillis;
                    }
                    long idleMillis                    = currentTimeMillis - lastActiveTimeMillis;
                    long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;
                    if (timeBetweenEvictionRunsMillis <= 0) {
                        timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
                    }
                    if (idleMillis >= timeBetweenEvictionRunsMillis
                            || idleMillis < 0 // unexcepted branch
                            ) {
                        boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
                        if (!validate) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("skip not validate connection.");
                            }
                            discardConnection(poolableConnection.holder);
                             continue;
                        }
                    }
                }
            }

這段代碼的邏輯是:參數(shù)testWhileIdle設(shè)置為true的話,檢查當(dāng)前鏈接的空閑時(shí)長如果大于timeBetweenEvictionRunsMillis(默認(rèn)60秒)的話,則調(diào)用testConnectionInternal測(cè)試連接可用性,連接不可用則關(guān)閉連接,continue重新獲取連接。

然后:

if (removeAbandoned) {
                StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
                poolableConnection.connectStackTrace = stackTrace;
                poolableConnection.setConnectedTimeNano();
                poolableConnection.traceEnable = true;
                activeConnectionLock.lock();
                try {
                    activeConnections.put(poolableConnection, PRESENT);
                } finally {
                    activeConnectionLock.unlock();
                }
            }

出現(xiàn)了一個(gè)removeAbandoned參數(shù),這個(gè)參數(shù)的意思是移除被遺棄的連接對(duì)象,如果打開的話就把當(dāng)前連接放到activeConnections中,篇幅有限,這部分內(nèi)容就不展開了,后面我們會(huì)專門寫一篇文章介紹removeAbandoned參數(shù)。

剩下的一小部分代碼,很簡單,根據(jù)參數(shù)設(shè)置連接的autoCommit,之后返回連接poolableConnection。

if (!this.defaultAutoCommit) {
                poolableConnection.setAutoCommit(false);
            }
            return poolableConnection;
        }
    }

getConnectionDirect方法源碼分析完成了,下面我們要看一下getConnectionInternal方法,這是真正從連接池中獲取連接的方法。

getConnectionInternal

直接看代碼:

private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
        if (closed) {
            connectErrorCountUpdater.incrementAndGet(this);
            throw new DataSourceClosedException("dataSource already closed at " + new Date(closeTimeMillis));
        }
        if (!enable) {
            connectErrorCountUpdater.incrementAndGet(this);
            if (disableException != null) {
                throw disableException;
            }
            throw new DataSourceDisableException();
        }
        final long nanos = TimeUnit.MILLISECONDS.toNanos(maxWait);
        final int maxWaitThreadCount = this.maxWaitThreadCount;
        DruidConnectionHolder holder;

檢查連接池狀態(tài)如果已經(jīng)disable或cloesed的話,拋異常。

接下來:

for (boolean createDirect = false;;) {
            if (createDirect) {
                createStartNanosUpdater.set(this, System.nanoTime());
                if (creatingCountUpdater.compareAndSet(this, 0, 1)) {
                    PhysicalConnectionInfo pyConnInfo = DruidDataSource.this.createPhysicalConnection();
                    holder = new DruidConnectionHolder(this, pyConnInfo);
                    holder.lastActiveTimeMillis = System.currentTimeMillis();
                    creatingCountUpdater.decrementAndGet(this);
                    directCreateCountUpdater.incrementAndGet(this);
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("conn-direct_create ");
                    }
                    boolean discard = false;
                    lock.lock();
                    try {
                        if (activeCount < maxActive) {
                            activeCount++;
                            holder.active = true;
                            if (activeCount > activePeak) {
                                activePeak = activeCount;
                                activePeakTime = System.currentTimeMillis();
                            }
                            break;
                        } else {
                            discard = true;
                        }
                    } finally {
                        lock.unlock();
                    }
                    if (discard) {
                        JdbcUtils.close(pyConnInfo.getPhysicalConnection());
                    }
                }
            }

初始化createDirect變量為false之后啟動(dòng)無限循環(huán),意思是不斷循環(huán)直到獲取到連接、或超時(shí)等其他異常情況發(fā)生。

緊接著的這段代碼是createDirect=true的情況下執(zhí)行的,createDirect是在下面循環(huán)體中檢查如果:createScheduler不為空、連接池空、活動(dòng)連接數(shù)小于設(shè)定的最大活動(dòng)連接數(shù)maxActive、并且createScheduler的隊(duì)列中排隊(duì)等待創(chuàng)建連接的線程大于0的情況下,設(shè)置createDirect為true的,以上這些條件如果成立的話,大概率表明createScheduler中的創(chuàng)建線程出問題了、所以createScheduler大概率指望不上了,所以要直接創(chuàng)建連接了。

直接創(chuàng)建的代碼也很容易理解,調(diào)用createPhysicalConnection創(chuàng)建物理連接,創(chuàng)建DruidConnectionHolder封裝該物理連接,創(chuàng)建之后獲取鎖資源,檢查activeCount < maxActive則表明創(chuàng)建連接成功、結(jié)束for循環(huán),否則,activeCount >= maxActive則說明違反了原則(直接創(chuàng)建連接的過程中createScheduler可能復(fù)活了、又創(chuàng)建出來連接放入連接池中了),所以,關(guān)閉鎖資源之后,將剛創(chuàng)建出來的連接關(guān)閉。

然后:

try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                connectErrorCountUpdater.incrementAndGet(this);
                throw new SQLException("interrupt", e);
            }
            try {
                if (maxWaitThreadCount > 0
                        && notEmptyWaitThreadCount >= maxWaitThreadCount) {
                    connectErrorCountUpdater.incrementAndGet(this);
                    throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
                            + lock.getQueueLength());
                }
                if (onFatalError
                        && onFatalErrorMaxActive > 0
                        && activeCount >= onFatalErrorMaxActive) {
                    connectErrorCountUpdater.incrementAndGet(this);
                    StringBuilder errorMsg = new StringBuilder();
                    errorMsg.append("onFatalError, activeCount ")
                            .append(activeCount)
                            .append(", onFatalErrorMaxActive ")
                            .append(onFatalErrorMaxActive);
                    if (lastFatalErrorTimeMillis > 0) {
                        errorMsg.append(", time '")
                                .append(StringUtils.formatDateTime19(
                                        lastFatalErrorTimeMillis, TimeZone.getDefault()))
                                .append("'");
                    }
                    if (lastFatalErrorSql != null) {
                        errorMsg.append(", sql \n")
                                .append(lastFatalErrorSql);
                    }
                    throw new SQLException(
                            errorMsg.toString(), lastFatalError);
                }
                connectCount++;
                if (createScheduler != null
                        && poolingCount == 0
                        && activeCount < maxActive
                        && creatingCountUpdater.get(this) == 0
                        && createScheduler instanceof ScheduledThreadPoolExecutor) {
                    ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) createScheduler;
                    if (executor.getQueue().size() > 0) {
                        createDirect = true;
                        continue;
                    }
                }

獲取鎖資源,檢查等待獲取連接的線程數(shù)如果大于參數(shù)設(shè)置的最大等待線程數(shù),拋異常。

檢查并處理異常。

累加connectCount。

之后是上面提到過的對(duì)createDirect的處理。

接下來到了最為關(guān)鍵的部分,一般情況下createDirect為false,不會(huì)直接創(chuàng)建連接,邏輯會(huì)走到下面這部分代碼中,從連接池中獲取連接:

if (maxWait > 0) {
                    holder = pollLast(nanos);
                } else {
                    holder = takeLast();
                }
                if (holder != null) {
                    if (holder.discard) {
                        continue;
                    }
                    activeCount++;
                    holder.active = true;
                    if (activeCount > activePeak) {
                        activePeak = activeCount;
                        activePeakTime = System.currentTimeMillis();
                    }
                }
            } catch (InterruptedException e) {
                connectErrorCountUpdater.incrementAndGet(this);
                throw new SQLException(e.getMessage(), e);
            } catch (SQLException e) {
                connectErrorCountUpdater.incrementAndGet(this);
                throw e;
            } finally {
                lock.unlock();
            }

如果參數(shù)設(shè)置了maxWait,則調(diào)用pollLast限時(shí)獲取,否則調(diào)用takeLast獲取連接,這兩個(gè)方法稍后分析。

之后檢查獲取到的連接已經(jīng)被discard的話,continue重新獲取連接。

釋放鎖資源。

從連接池中獲取到了連接,結(jié)束for循環(huán)。

如果takeLast或poolLast返回的DruidConnectionHolder為null的話(調(diào)用poolLast超時(shí)),處理錯(cuò)誤信息,拋GetConnectionTimeoutException超時(shí)異常(這部分代碼沒有貼出,省略了......感興趣的童鞋自己打開源碼看一下)。

否則,用DruidConnectionHolder封裝創(chuàng)建DruidPooledConnection后返回。

takeLast & pollLast(nanos)

這兩個(gè)方法的邏輯其實(shí)差不多,主要區(qū)別一個(gè)是限時(shí),一個(gè)不限時(shí),兩個(gè)方法都是在鎖狀態(tài)下執(zhí)行。

具體調(diào)用哪一個(gè)方法取決于參數(shù)maxWait,默認(rèn)值為-1,默認(rèn)情況下會(huì)調(diào)用takeLast,獲取連接的時(shí)候不限時(shí)。

建議設(shè)置maxWait,否則在特殊情況下如果創(chuàng)建連接失敗、會(huì)導(dǎo)致應(yīng)用層線程掛起,獲取不到任何返回的情況出現(xiàn)。如果設(shè)置了maxWait,getConnection方法會(huì)調(diào)用pollLast(nanos),獲取不到連接后,應(yīng)用層會(huì)得到連接超時(shí)的反饋。

先看takeLast方法:

takeLast() throws InterruptedException, SQLException {
        try {
            while (poolingCount == 0) {
                emptySignal(); // send signal to CreateThread create connection
                if (failFast && isFailContinuous()) {
                    throw new DataSourceNotAvailableException(createError);
                }
                notEmptyWaitThreadCount++;
                if (notEmptyWaitThreadCount > notEmptyWaitThreadPeak) {
                    notEmptyWaitThreadPeak = notEmptyWaitThreadCount;
                }
                try {
                    notEmpty.await(); // signal by recycle or creator
                } finally {
                    notEmptyWaitThreadCount--;
                }
                notEmptyWaitCount++;
                if (!enable) {
                    connectErrorCountUpdater.incrementAndGet(this);
                    if (disableException != null) {
                        throw disableException;
                    }
                    throw new DataSourceDisableException();
                }
            }
        } catch (InterruptedException ie) {
            notEmpty.signal(); // propagate to non-interrupted thread
            notEmptySignalCount++;
            throw ie;
        }
        decrementPoolingCount();
        DruidConnectionHolder last = connections[poolingCount];
        connections[poolingCount] = null;
        return last;
    }

如果連接池為空(poolingCount == 0)的話,無限循環(huán)。

調(diào)用emptySignal(),通知?jiǎng)?chuàng)建連接線程,有人在等待獲取連接,抓緊時(shí)間創(chuàng)建連接。

然后調(diào)用notEmpty.await(),等待創(chuàng)建連接線程在完成創(chuàng)建、或者有連接歸還到連接池中后喚醒通知。

如果發(fā)生異常,調(diào)用一下notEmpty.signal()通知其他獲取連接的線程,沒準(zhǔn)自己沒能獲取成功、其他線程能獲取成功。

下面的代碼,線程池一定不空了。

線程池的線程數(shù)量減1(decrementPoolingCount),然后獲取connections的最后一個(gè)元素返回。

pollLast方法的代碼邏輯和takeLast的類似,只不過線程池空的話,當(dāng)前線程會(huì)限時(shí)掛起等待,超時(shí)仍然不能獲取到連接的話,直接返回null。

Druid連接池獲取連接代碼分析完畢!

小結(jié)

Druid連接池的連接獲取過程的源碼分析完畢,后面還有連接歸還過程,更多關(guān)于java Druid獲取連接getConnection的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Spring 緩存抽象示例詳解

    Spring 緩存抽象示例詳解

    Spring框架自身并沒有實(shí)現(xiàn)緩存解決方案,但是從3.1開始定義了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口,提供對(duì)緩存功能的聲明,能夠與多種流行的緩存實(shí)現(xiàn)集成。這篇文章主要介紹了Spring 緩存抽象 ,需要的朋友可以參考下
    2018-09-09
  • Spring依賴注入的兩種方式(根據(jù)實(shí)例詳解)

    Spring依賴注入的兩種方式(根據(jù)實(shí)例詳解)

    這篇文章主要介紹了Spring依賴注入的兩種方式(根據(jù)實(shí)例詳解),非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2017-05-05
  • 簡單實(shí)現(xiàn)Spring的IOC原理詳解

    簡單實(shí)現(xiàn)Spring的IOC原理詳解

    這篇文章主要介紹了簡單實(shí)現(xiàn)Spring的IOC原理詳解,具有一定借鑒價(jià)值,需要的朋友可以參考下。
    2017-12-12
  • java?抽象類示例詳解

    java?抽象類示例詳解

    我們將“只有方法聲明,沒有方法體”的一類方法統(tǒng)稱為抽象方法,抽象方法用關(guān)鍵字abstract修飾,本文介紹java?抽象類示例詳解,感興趣的朋友跟隨小編一起看看吧
    2024-12-12
  • 詳解lambda表達(dá)式foreach性能分析

    詳解lambda表達(dá)式foreach性能分析

    這篇文章主要介紹了詳解lambda表達(dá)式foreach性能分析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • SpringBoot配置文件、多環(huán)境配置、讀取配置的4種實(shí)現(xiàn)方式

    SpringBoot配置文件、多環(huán)境配置、讀取配置的4種實(shí)現(xiàn)方式

    SpringBoot支持多種配置文件位置和格式,其中application.properties和application.yml是默認(rèn)加載的文件,配置文件可以根據(jù)環(huán)境通過spring.profiles.active屬性進(jìn)行區(qū)分,命令行參數(shù)具有最高優(yōu)先級(jí),可覆蓋其他所有配置
    2024-09-09
  • lombok?子類中如何使用@Builder問題

    lombok?子類中如何使用@Builder問題

    這篇文章主要介紹了lombok?子類中如何使用@Builder問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • 微信小程序調(diào)用微信登陸獲取openid及java做為服務(wù)端示例

    微信小程序調(diào)用微信登陸獲取openid及java做為服務(wù)端示例

    這篇文章主要介紹了微信小程序調(diào)用微信登陸獲取openid及java做為服務(wù)端示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • java選擇框、單選框和單選按鈕

    java選擇框、單選框和單選按鈕

    本文給大家介紹的是java中選擇框、單選框和單選按鈕的操作方法,十分的簡單實(shí)用,有需要的小伙伴可以參考下。
    2015-06-06
  • Java實(shí)現(xiàn)手寫乞丐版線程池的示例代碼

    Java實(shí)現(xiàn)手寫乞丐版線程池的示例代碼

    在這篇文章當(dāng)中我們主要介紹實(shí)現(xiàn)一個(gè)非常簡易版的線程池,深入的去理解其中的原理,麻雀雖小,五臟俱全,感興趣的小伙伴快跟隨小編一起學(xué)習(xí)學(xué)習(xí)吧
    2022-10-10

最新評(píng)論