連接攔截器中用到的復(fù)用連接池ConnectionPool

OkHttp的ConnectInterceptor連接攔截器剖析:http://www.reibang.com/p/f90aa5894cdf
連接池ConnectionPool用來(lái)管理和復(fù)用連接,但是在有限的時(shí)間內(nèi)復(fù)用鏈接的。
一進(jìn)來(lái)會(huì)創(chuàng)建一個(gè)線程池:

/**
     * 后臺(tái)線程用于清除過(guò)期的連接
     *
     * 每個(gè)連接池最多只能運(yùn)行一個(gè)線程
     *
     * 線程池執(zhí)行器允許對(duì)池本身進(jìn)行垃圾收集
     */
    private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
            Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));

上面的注釋是從官方注釋翻譯過(guò)來(lái)的說(shuō)每個(gè)連接池最多只能運(yùn)行一個(gè)線程趣兄,這個(gè)是怎么執(zhí)行的呢
來(lái)看這段代碼:

void put(RealConnection connection) {
        assert (Thread.holdsLock(this));
        // 沒(méi)有任何連接時(shí),cleanupRunning = false;
        // 即沒(méi)有任何鏈接時(shí)才會(huì)去執(zhí)行executor.execute(cleanupRunnable);
        // 從而保證每個(gè)連接池最多只能運(yùn)行一個(gè)線程妹蔽。
        if (!cleanupRunning) {
            cleanupRunning = true;
            executor.execute(cleanupRunnable); // 做異步處理任務(wù)
        }
        connections.add(connection); // 將連接加入到雙端隊(duì)列
    }

put方法是連接池中的存儲(chǔ)方法跳纳,在ConnectInterceptor-->intercept-->streamAllocation.newStream-->findHealthyConnection-->創(chuàng)建新鏈接后調(diào)用茶敏,進(jìn)行存儲(chǔ)健康的連接窟坐。

什么是雙端隊(duì)列呢叉抡?

// 連接池中維護(hù)了一個(gè)雙端隊(duì)列Deque來(lái)存儲(chǔ)連接
    private final Deque<RealConnection> connections = new ArrayDeque<>();

接下來(lái)看一下它的成員變量和構(gòu)造函數(shù):

/** 每個(gè)地址的最大空閑連接數(shù). */
    private final int maxIdleConnections; // 默認(rèn) 5
    // 每個(gè)keep-alive時(shí)長(zhǎng)為5分鐘
    private final long keepAliveDurationNs; // 默認(rèn)5分鐘

/**
     * Create a new connection pool with tuning parameters appropriate for a single-user application.
     * The tuning parameters in this pool are subject to change in future OkHttp releases. Currently
     * this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity.
     * TODO 連接池最多保持5個(gè)地址的連接keep-alive猬膨,每個(gè)keep-alive時(shí)長(zhǎng)為5分鐘
     */
    public ConnectionPool() {
        this(5, 5, TimeUnit.MINUTES);
    }

    public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
        this.maxIdleConnections = maxIdleConnections;
        this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

        // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
        // 在保持活躍狀態(tài)的持續(xù)時(shí)間內(nèi)放置任務(wù)角撞,否則將循環(huán)清理
        if (keepAliveDuration <= 0) {
            throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
        }
    }

連接池的get方法:

/**
     * Returns a recycled connection to {@code address}, or null if no such connection exists.
     * 返回連接,如果不存在此類連接,則返回空值
     * The route is null if the address has not yet been routed.
     * 如果地址尚未路由靴寂,則路由為空
     */
    @Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
        assert (Thread.holdsLock(this));
        for (RealConnection connection : connections) {
            if (connection.isEligible(address, route)) { // 判斷連接是否可用
                streamAllocation.acquire(connection, true);
                return connection;
            }
        }
        return null;
    }

連接池的清除原理:
在類中創(chuàng)建了一個(gè)Runnable:

private final Runnable cleanupRunnable = new Runnable() {
        @Override public void run() {
            while (true) {
                long waitNanos = cleanup(System.nanoTime()); // cleanup方法里面就是具體的GC回收算法磷蜀,類似于GC的標(biāo)記清除算法
                if (waitNanos == -1) return;
                if (waitNanos > 0) {
                    long waitMillis = waitNanos / 1000000L;
                    waitNanos -= (waitMillis * 1000000L);
                    synchronized (ConnectionPool.this) {
                        try {
                            ConnectionPool.this.wait(waitMillis, (int) waitNanos);
                        } catch (InterruptedException ignored) {
                        }
                    }
                }
            }
        }
    };

在put的時(shí)候回執(zhí)行這個(gè)線程。

這個(gè)Runnable的潤(rùn)方法中執(zhí)行了cleanup方法百炬,這個(gè)方法就是具體的回收方法褐隆,里面主要使用標(biāo)記清除算法。

/**
     * Performs maintenance on this pool, evicting the connection that has been idle the longest if
     * either it has exceeded the keep alive limit or the idle connections limit.
     * 對(duì)該池執(zhí)行維護(hù)剖踊,如果已超過(guò)保持活動(dòng)狀態(tài)限制或空閑連接限制庶弃,則清除空閑時(shí)間最長(zhǎng)的連接
     *
     * <p>Returns the duration in nanos to sleep until the next scheduled call to this method.
     * 返回在下次計(jì)劃調(diào)用此方法之前休眠的持續(xù)時(shí)間(納秒)
     *
     * Returns -1 if no further cleanups are required.
     * 如果不需要進(jìn)一步清理,則返回-1
     *
     */
    long cleanup(long now) {
        int inUseConnectionCount = 0;
        int idleConnectionCount = 0;
        RealConnection longestIdleConnection = null;
        long longestIdleDurationNs = Long.MIN_VALUE;

        // Find either a connection to evict, or the time that the next eviction is due.
        // 遍歷隊(duì)列當(dāng)中所有的RealConnection集合德澈,去標(biāo)記泄露或者不活躍的連接
        synchronized (this) {
            for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
                RealConnection connection = i.next();

                // If the connection is in use, keep searching.
                // 如果連接正在使用中歇攻,請(qǐng)繼續(xù)搜索
                if (pruneAndGetAllocationCount(connection, now) > 0) {
                    inUseConnectionCount++;
                    continue;
                }

                idleConnectionCount++;

                // If the connection is ready to be evicted, we're done.
                // 如果連接準(zhǔn)備好被收回,標(biāo)記為空閑連接
                long idleDurationNs = now - connection.idleAtNanos;
                if (idleDurationNs > longestIdleDurationNs) {
                    longestIdleDurationNs = idleDurationNs;
                    longestIdleConnection = connection;
                }
            }

            // 如果被標(biāo)記的連接滿足空閑的socekt連接超過(guò)5個(gè)
            if (longestIdleDurationNs >= this.keepAliveDurationNs
                    || idleConnectionCount > this.maxIdleConnections) {
                // We've found a connection to evict. Remove it from the list, then close it below (outside
                // of the synchronized block).
                // 如果空閑連接超過(guò)5個(gè)或者keepalive時(shí)間大于5分鐘梆造,則將該連接清理掉缴守,然后在下面關(guān)閉它(同步塊外部)
                connections.remove(longestIdleConnection); // 這時(shí)候就會(huì)把連接從集合中移除并關(guān)閉
            } else if (idleConnectionCount > 0) {
                // A connection will be ready to evict soon.
                return keepAliveDurationNs - longestIdleDurationNs; // 返回此連接的到期時(shí)間,供下次進(jìn)行清理
            } else if (inUseConnectionCount > 0) {
                // All connections are in use. It'll be at least the keep alive duration 'til we run again.
                return keepAliveDurationNs; // 全部都是活躍連接镇辉,5分鐘時(shí)候再進(jìn)行清理
            } else { // 沒(méi)有連接
                // No connections, idle or in use.
                cleanupRunning = false;
                return -1;  // 返回-1 跳出循環(huán)
            }
        }

        // 關(guān)閉連接屡穗,返回時(shí)間0,立即再次進(jìn)行清理
        closeQuietly(longestIdleConnection.socket());

        // Cleanup again immediately.
        // 立即再次清理
        return 0;
    }

如何找到最不活躍的鏈接呢忽肛?
在RealConnection里有個(gè)StreamAllocation虛引用列表村砂,每創(chuàng)建一個(gè)StreamAllocation,就會(huì)把它添加進(jìn)該列表中屹逛,如果留關(guān)閉以后就將StreamAllocation對(duì)象從該列表中移除础废,正是利用這種引用計(jì)數(shù)的方式判定一個(gè)連接是否為空閑連接.

public final class RealConnection extends Http2Connection.Listener implements Connection {
    /**
     * Current streams carried by this connection.
     * 由此連接攜帶的當(dāng)前流。
     */
    public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
}

pruneAndGetAllocationCount方法:

/**
     * Prunes any leaked allocations and then returns the number of remaining live allocations on {@code connection}
     * 刪除任何泄漏的分配罕模,然后返回@code connection上剩余的活動(dòng)分配數(shù)
     *
     * Allocations are leaked if the connection is tracking them but the application code has abandoned them.
     * 如果連接正在跟蹤分配评腺,但應(yīng)用程序代碼已放棄分配,則會(huì)泄漏分配
     *
     * Leak detection is imprecise and relies on garbage collection.
     * 泄漏檢測(cè)不精確手销,依賴于垃圾收集歇僧。
     *
     * 如何找到最不活躍的鏈接呢
     */
    private int pruneAndGetAllocationCount(RealConnection connection, long now) {
        // 虛引用列表
        List<Reference<StreamAllocation>> references = connection.allocations;
        // 遍歷虛引用列表
        for (int i = 0; i < references.size(); ) {
            Reference<StreamAllocation> reference = references.get(i);
            // 如果虛引用StreamAllocation正在被使用图张,則跳過(guò)進(jìn)行下一次循環(huán)
            if (reference.get() != null) {
                i++; // 引用計(jì)數(shù)
                continue;
            }

            // We've discovered a leaked allocation. This is an application bug.
            // 我們發(fā)現(xiàn)了一個(gè)泄露的分配锋拖。這是一個(gè)應(yīng)用程序bug
            StreamAllocation.StreamAllocationReference streamAllocRef =
                    (StreamAllocation.StreamAllocationReference) reference;
            String message = "A connection to " + connection.route().address().url()
                    + " was leaked. Did you forget to close a response body?";
            Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace);

            references.remove(i);
            connection.noNewStreams = true;

            // If this was the last allocation, the connection is eligible for immediate eviction.
            // 如果所有的StreamAllocation引用都沒(méi)有了,返回引用計(jì)數(shù)0
            if (references.isEmpty()) {
                connection.idleAtNanos = now - keepAliveDurationNs;
                return 0; // 表示這個(gè)連接沒(méi)有代碼引用了
            }
        }

        return references.size(); // 返回剩余的活動(dòng)分配數(shù) (返回引用列表的大小祸轮,作為引用計(jì)數(shù))
    }

以上就是復(fù)用連接池的核心代碼兽埃。
OkHttp的CallServerInterceptor請(qǐng)求服務(wù)器攔截器剖析:http://www.reibang.com/p/9e402c33b322

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市适袜,隨后出現(xiàn)的幾起案子柄错,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件售貌,死亡現(xiàn)場(chǎng)離奇詭異给猾,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)颂跨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門敢伸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人恒削,你說(shuō)我怎么就攤上這事池颈。” “怎么了钓丰?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵躯砰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我携丁,道長(zhǎng)琢歇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任梦鉴,我火速辦了婚禮矿微,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尚揣。我一直安慰自己涌矢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布快骗。 她就那樣靜靜地躺著娜庇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪方篮。 梳的紋絲不亂的頭發(fā)上名秀,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音藕溅,去河邊找鬼匕得。 笑死,一個(gè)胖子當(dāng)著我的面吹牛巾表,可吹牛的內(nèi)容都是我干的汁掠。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼集币,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼考阱!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起鞠苟,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤乞榨,失蹤者是張志新(化名)和其女友劉穎秽之,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吃既,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡考榨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鹦倚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片董虱。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖申鱼,靈堂內(nèi)的尸體忽然破棺而出愤诱,到底是詐尸還是另有隱情,我是刑警寧澤捐友,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布淫半,位于F島的核電站,受9級(jí)特大地震影響匣砖,放射性物質(zhì)發(fā)生泄漏科吭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一猴鲫、第九天 我趴在偏房一處隱蔽的房頂上張望对人。 院中可真熱鬧,春花似錦拂共、人聲如沸牺弄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)势告。三九已至,卻和暖如春抚恒,著一層夾襖步出監(jiān)牢的瞬間咱台,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工俭驮, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留回溺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓混萝,卻偏偏與公主長(zhǎng)得像遗遵,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子譬圣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • Okhttp的5個(gè)內(nèi)置攔截器可以說(shuō)是Okhttp的核心瓮恭,因?yàn)檎麄€(gè)請(qǐng)求的過(guò)程都被封裝在這5個(gè)攔截器里面雄坪。而5個(gè)攔截器...
    有興不虛昧閱讀 306評(píng)論 0 0
  • Okhttp 基礎(chǔ)知識(shí)導(dǎo)圖 Okhttp 使用1厘熟,創(chuàng)建一個(gè)客戶端屯蹦。2,創(chuàng)建一個(gè)請(qǐng)求绳姨。3登澜,發(fā)起請(qǐng)求(入?yún)⒒卣{(diào))。 一...
    gczxbb閱讀 2,059評(píng)論 0 2
  • 本篇文章為okhttp源碼學(xué)習(xí)筆記系列的第二篇文章飘庄,本篇文章的主要內(nèi)容為okhttp中的連接與連接的管理脑蠕,因此需要...
    蕉下孤客閱讀 2,725評(píng)論 6 24
  • 在一個(gè)方法內(nèi)部定義的變量都存儲(chǔ)在棧中,當(dāng)這個(gè)函數(shù)運(yùn)行結(jié)束后跪削,其對(duì)應(yīng)的棧就會(huì)被回收谴仙,此時(shí),在其方法體中定義的變量將不...
    Y了個(gè)J閱讀 4,420評(píng)論 1 14
  • 上周我寫了一篇文章講了我老公和大女兒的一些趣事碾盐,小女兒看到這篇文章后晃跺,很是不平,抗議說(shuō)毫玖,太不公平掀虎,沒(méi)有提到她。我只...
    一切都那么美好閱讀 230評(píng)論 0 2