<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Tomcat JDBC Pool 源碼實(shí)現(xiàn)簡(jiǎn)單分析

          共 4541字,需瀏覽 10分鐘

           ·

          2022-06-08 15:54

          點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)

          轉(zhuǎn)自:sp42a

          鏈接:https://zhangxin.blog.csdn.net/article/details/124901850

          什么是連接池?

          池,不由自主的會(huì)想到水池。小時(shí)候,我們都要去遠(yuǎn)處的水井挑水,倒進(jìn)家中的水池里面。這樣,每次要用水時(shí),直接從水池中「取」就行了。不用大老遠(yuǎn)跑去水井打水。數(shù)據(jù)庫連接池就如此,我們預(yù)先準(zhǔn)備好一些連接,放到池中。當(dāng)需要時(shí),就直接獲取。而不要每次跟數(shù)據(jù)庫建立一個(gè)新的連接。特別對(duì)數(shù)據(jù)庫連接這類耗時(shí),耗資源的操作。當(dāng)連接用完后,再放回池中,供后續(xù)使用。

          連接池的作用?

          避免多次去創(chuàng)建資源。例如,創(chuàng)建新的數(shù)據(jù)庫連接,500ms 輕輕松松就消耗了。建立 TCP 連接,數(shù)據(jù)庫賬號(hào)驗(yàn)證等等。這性能消耗起來,可是非常大的。在稍大的系統(tǒng)內(nèi),連接池是必備的。同時(shí),對(duì)技術(shù)人員要求,對(duì)連接池的掌握也是必須的。

          tomcat-jdbc-pool 的特色

          基于 jdk1.5 后的并發(fā)實(shí)現(xiàn)。代碼簡(jiǎn)潔,精練。核心的類就2,3個(gè)。對(duì)池的控制就在 org.apache.tomcat.jdbc.pool.ConnectionPool 中搞定。

          先前有簡(jiǎn)單看過 dbcp1.x, c3p0 等等,代碼量真不少,邏輯復(fù)雜。想熟悉池的設(shè)計(jì),可以仔細(xì)讀讀 tomcat-jdbc-pool,非??焖俚娜胧?。在 dbcp2 的實(shí)現(xiàn)時(shí),跟 tomcat-jdbc-pool 思路一致(完全 copy 的版本)。對(duì)于連接池來說,最基本的特點(diǎn)就是:

          • 有一定的容量,及已經(jīng)創(chuàng)建好的對(duì)象

          • 有「借」有「還」操作的接口

          池中「借出」連接是怎么個(gè)過程?

          在 jdbc-pool 設(shè)計(jì)有 2 隊(duì)列,分別為 busy 和 idle,存儲(chǔ)「正在使用」和「空閑」的連接。都采用 ArrayBlockingQueue 以保證線程安全。

          • 當(dāng)有請(qǐng)求「借」的動(dòng)作過來時(shí),從 idle 中 poll 一個(gè)連接,然后將該連接再 offer 至 busy 隊(duì)列中。這是最基本最純凈的思路。

          • 當(dāng) idle 連接不夠時(shí),內(nèi)部會(huì)再去創(chuàng)建新的連接返回給客戶端。

          但是,做為「池」必須的職責(zé)之一是控制總量,不會(huì)任你去增長。那么,有意思來了,他是怎么控制總量的咧?我們可以通俗點(diǎn)稱『占坑法』(tomcat 中也有不少場(chǎng)景采用這方式)。

          首先池中有維護(hù)連接數(shù)總量「計(jì)數(shù)器」(采用 AtomicInteger 保證線程安全,每次新增或銷毀都會(huì)變更)?!赫伎臃ā痪驮诿看我聞?chuàng)建連接池,先總量計(jì)數(shù)器+1(占位),再比較是否達(dá)到配置的池的最大連接數(shù)。如果沒有達(dá)到,則創(chuàng)建新的;如果已達(dá)到了,則等待現(xiàn)有連接釋放,再取走。有點(diǎn)類似,大學(xué)時(shí)先用本書去搶位置占著。大致實(shí)現(xiàn)代碼【已經(jīng)對(duì)源碼簡(jiǎn)化】如下:

          public class ConnectionPool {
          // 連接數(shù)的總量
          private AtomicInteger size = new AtomicInteger(0);

          // 所有正在使用中的連接
          private BlockingQueue<PooledConnection> busy;

          // 所有空閑的連接
          private BlockingQueue<PooledConnection> idle;
          PooledConnection con = idle.poll();//

          while (true) {
          if (con != null) {// 如果從空閑連接隊(duì)列中取出的連接不為空
          // 把這個(gè)連接加入正在使用中的連接列表,***并返回
          busy.offer(con);// 這簡(jiǎn)化了,在源碼中這兒會(huì)對(duì)連接進(jìn)行校驗(yàn)、檢查或進(jìn)行連接。
          return con;
          }
          // 下面判斷空閑連接隊(duì)列中取出的連接為空的情況
          // 沒有超出連接總數(shù)
          if (size.get() < getPoolProperties().getMaxActive()) {

          // 占坑神技
          if (size.addAndGet(1) > getPoolProperties().getMaxActive()) {// 超出連接總數(shù)
          // 既然沒了,那數(shù)量也減回去
          // 再去等待其他連接歸還回來
          size.decrementAndGet();
          } else {
          //新建一個(gè)物理連接
          return createConnection(now, con, username, password);
          }
          }

          // 等待一定時(shí)間 timetowait 后,再次從空閑連接隊(duì)列中取出的連接,循環(huán)執(zhí)行上面過程
          con = idle.poll(timetowait, TimeUnit.MILLISECONDS);
          }
          }}

          用完后「歸還」連接是怎么個(gè)過程?

          大致思路跟「借」操作相反落。當(dāng)然是無視那些「善后」的工作,只關(guān)注資源的管理。但是,做為連接池必須的職責(zé)之一,并不真實(shí)的斷開與數(shù)據(jù)庫的連接。而只是放至 idle 隊(duì)列中,供客戶端下次再使用。如果有需要或必要肯定會(huì)釋放,技巧所在。大致代碼如下:

          protected void returnConnection(PooledConnection con) {
          if (con != null) {
          try {
          con.lock();

          if (busy.remove(con)) {
          // 跟允許的最大空閑數(shù)比較
          if(idle.size() < poolProperties.getMaxIdle()) {
          idle.offer(con);
          // 源碼中調(diào)用 release
          // 會(huì)根據(jù)配置項(xiàng)執(zhí)行一些校驗(yàn),例如:testOnReturn 為 true,則在回收時(shí)檢查連接是否正常
          //release(con);
          }
          } catch(Exception e) {
          //....
          } finally {
          con.unlock();
          }
          } //end if}


          當(dāng)長時(shí)間運(yùn)行后,怎么回收無效的連接?

          這是連接池必備的功能之一,類似檢查死鏈或者釋放自身過多的資源。比如,在高并發(fā)過后,對(duì)資源消耗量少時(shí),就釋放些不再使用的數(shù)據(jù)庫連接(真實(shí)斷開),維護(hù)合理的空格數(shù)量?!?** 連接池的minIdle定義了這個(gè)數(shù)量】

          看到這應(yīng)用場(chǎng)景就自然想到,通過后臺(tái)線程定時(shí)掃描。——對(duì)的,就是這樣子。」——同樣在 ConnectionPool 這個(gè)類文件中的 PoolCleaner 類。寫在同個(gè)類文件中,便于用 this 進(jìn)行傳遞數(shù)據(jù)。不用再去構(gòu)造個(gè)復(fù)雜的 ConnectionPool 對(duì)象。

          直接上代碼,「好代碼」就是最好的描述。

          public class ConnectionPool {

          /**
          * Initialize the connection pool - called from the constructor
          */

          protected void init(PoolConfiguration properties) throws SQLException {
          initializePoolCleaner(properties);
          }

          public void initializePoolCleaner(PoolConfiguration properties) {
          if (properties.isPoolSweeperEnabled()) {
          poolCleaner = new PoolCleaner(this, properties.getTimeBetweenEvictionRunsMillis());
          poolCleaner.start(); //只注冊(cè)一個(gè)清理器,并未啟動(dòng)線程。
          } //end if
          }

          /**
          * 檢查所有的空閑連接
          */

          public void checkIdle(boolean ignoreMinSize) {

          try {
          if (idle.size()==0) return;

          Iterator<PooledConnection> unlocked = idle.iterator();

          //【重點(diǎn):如果空閑數(shù)量大于屬性MinIdle,則執(zhí)行清理!】
          while ( (idle.size()>=getPoolProperties().getMinIdle())) &&unlocked.hasNext()) {
          PooledConnection con = unlocked.next();
          try {
          con.lock();
          //如果這時(shí)已到busy中,則不檢查了
          if (busy.contains(con)) {
          continue;
          }

          release(con);//釋放了物理連接
          idle.remove(con);//從空閑隊(duì)列中移除

          } finally {
          con.unlock();
          }
          } //while
          } catch (Exception e) {
          //....
          }

          }

          private static volatile Timer poolCleanTimer = null;
          private static HashSet<PoolCleaner> cleaners = new HashSet<>();

          //注冊(cè)一個(gè)清理器
          private static synchronized void registerCleaner(PoolCleaner cleaner) {
          unregisterCleaner(cleaner);
          cleaners.add(cleaner);

          //一堆構(gòu)造方式。。。
          if (poolCleanTimer == null) {
          ClassLoader loader = Thread.currentThread().getContextClassLoader();
          try {
          Thread.currentThread().setContextClassLoader(ConnectionPool.class.getClassLoader());
          poolCleanTimer = new Timer("PoolCleaner["+ System.identityHashCode(ConnectionPool.class.getClassLoader()) + ":"+
          System.currentTimeMillis() + "]", true);
          }finally {
          Thread.currentThread().setContextClassLoader(loader);
          }
          }
          //構(gòu)造定時(shí)掃描器
          //java有內(nèi)庫非常強(qiáng)大,想用啥有啥呀
          poolCleanTimer.scheduleAtFixedRate(cleaner, cleaner.sleepTime,cleaner.sleepTime);
          }

          //真實(shí)的處理線程在這兒。。。
          protected static class PoolCleaner extends TimerTask {
          protected WeakReference<ConnectionPool> pool;

          PoolCleaner(ConnectionPool pool, long sleepTime) {
          //弱引用,不了解的可以google下
          this.pool = new WeakReference<>(pool);
          }

          @Override
          public void run() {
          ConnectionPool pool = this.pool.get();
          if (pool == null) {
          stopRunning();
          } else if (!pool.isClosed()) {

          if (pool.getPoolProperties().getMinIdle() < pool.idle.size()) {
          pool.checkIdle(); //【重點(diǎn),如果空閑數(shù)量大于MinIdle,則執(zhí)行checkIdle】
          }
          }
          }

          public void start() {
          registerCleaner(this); //并未啟動(dòng)線程,只是注冊(cè)一個(gè)清理器
          }

          public void stopRunning() {
          unregisterCleaner(this);
          }
          }}

          ????

          1、拖動(dòng)文件就能觸發(fā)7-Zip安全漏洞,波及所有版本

          2、進(jìn)程切換的本質(zhì)是什么?

          3、一次 SQL 查詢優(yōu)化原理分析:900W+ 數(shù)據(jù),從 17s 到 300ms

          4、Redis數(shù)據(jù)結(jié)構(gòu)為什么既省內(nèi)存又高效?

          5、IntelliJ IDEA快捷鍵大全 + 動(dòng)圖演示

          6、全球第三瀏覽器,封殺中國用戶這種操作?。ㄎ哪┧蜁?/a>

          點(diǎn)

          點(diǎn)

          點(diǎn)點(diǎn)

          點(diǎn)

          瀏覽 46
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩伦理色片一区二区 | 天天爱激情 | 久久久久久成人无码 | 国产六区 | 靠逼免费视频 |