關于對象池的一些分析

KathieGrady 8年前發布 | 5K 次閱讀 安卓開發 Android開發 移動開發

在日常的開發工作中,我們可能使用或者聽說過對象池,線程池以及連接池。本文將介紹對象池的產生緣由,具體實現細節,以及需要注意的問題。

什么是對象池(模式)

  • 對象池(模式)是一種創建型設計模式
  • 它持有一個初始化好的對象的集合,將對象提供給調用者。

對象池的目的

  • 減少頻繁創建和銷毀對象帶來的成本,實現對象的緩存和復用

什么條件下使用對象池

  • 創建對象的成本比較大,并且創建比較頻繁。比如線程的創建代價比較大,于是就有了常用的線程池。

對象池的例子

Android中使用對象池的應用有很多,比如下面的這些都是應用了該模式

  • Handler處理的Message
  • 線程池執行器ThreadPoolExecutor
  • 控件TabLayout
  • 控制TypedArray的Resources

以一個簡單的獲取SytledAttributions代碼為例,展示一下對象池的應用

// Text colors/sizes come from the text appearance first
final TypedArray ta = context.obtainStyledAttributes(mTabTextAppearance,
    android.support.v7.appcompat.R.styleable.TextAppearance);
try {
    mTabTextSize = ta.getDimensionPixelSize(
            android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize, 0);
    mTabTextColors = ta.getColorStateList(
            android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor);
} finally {
    ta.recycle();
}

想必這段代碼都可能寫過,那就是在一開始的時候,我們都會被告誡:使用TypedArray結束的時候,一定要調用它的recycle方法。

回想起來,當時自己還很疑惑為什么要這么規定,其實很簡單,它使用了對象池。

調用者通過obtain方法從對象池中獲取對象,然后使用完畢后,需要使用recycle方法返還給對象池。

三種角色

上面的介紹中,也或多或少提到了下面的三種角色

  • Reusable 可重用的對象
  • Client 調用者
  • ReusablePool 可重用的對象的池

Reusable

  • 創建的成本較大,比如線程或者數據庫連接
  • 被ReusablePool持有
  • 被Client消費使用,使用完成應該被返回到ReusablePool

ReusablePool

  • 維護一定數量的Reusable,提供給客戶端使用
  • 提供 aquire 或者 obtain 等方法,便于客戶端請求Reusable
  • 提供 recycle 或者 release 等方法,便于客戶端使用完畢后,將Reusable對象奉還。

Client

  • 請求ReusablePool或者Reusable對象
  • 使用完畢Reusable對象后,返回給ReusablePool

對象池無可用的對象時,再次對象請求,可能的表現行為

  • 如果池的大小可以增長,創建新的對象并返回給client
  • 阻塞client調用,直到有可用的對象回收并返回
  • 拋出異常,通知client
  • 返回null給client

同步處理

在多線程的環境下,我們也會使用對象池。因此做好必要的同步是必須的。

要進行同步處理的通常是這兩個方法

  • aquire或obtain 負責返回對象
  • release或recycle 負責回收對象

下面是一段進行同步處理了的對象池的源碼。

public static class SynchronizedPool<T> extends SimplePool<T> {
        private final Object mLock = new Object();


        /**
         * Creates a new instance.
         *
         * @param maxPoolSize The max pool size.
         *
         * @throws IllegalArgumentException If the max pool size is less than zero.
         */
        public SynchronizedPool(int maxPoolSize) {
            super(maxPoolSize);
        }


        @Override
        public T acquire() {
            synchronized (mLock) {
                return super.acquire();
            }
        }


        @Override
        public boolean release(T element) {
            synchronized (mLock) {
                return super.release(element);
            }
        }
}

上述代碼為Android中 android.support.v4.util 提供的Pools中的 SynchronizedPool 的實現,它使用了synchronized關鍵字實現同步問題。

對象池與單例模式

為了統一管理對象,建議將對象池設為單例。

應用單例模式的時候,需要確保在多線程并發的情況下保持唯一的實例創建,具體實現方案,可以參考單例這種設計模式

池的大小選擇

  • 通常情況下,我們需要控制對象池的大小
  • 如果對象池沒有限制,可能導致對象池持有過多的閑置對象,增加內存的占用
  • 如果對象池閑置過小,沒有可用的對象時,會造成之前 對象池無可用的對象時,再次請求 出現的問題
  • 對象池的大小選取應該結合具體的使用場景,結合數據(觸發池中無可用對象的頻率)分析來確定。

空間換時間的折中

  • 本質上,對象池屬于空間換時間的折中
  • 它通過緩存初始化好的對象來提升調用者請求對象的響應速度。
  • 除此之外,折中(tradeoff)是軟件開發中的一個重要的概念,會貫穿整個軟件開發過程中。

對象池好處

  • 提升了client獲取對象的響應速度,比如單個線程和資源連接的創建成本都比較大。
  • 一定程度上減少了GC的壓力。
  • 對于實時性要求較高的程序有很大的幫助

對象池弊端

臟對象的問題

所謂的臟對象就是指的是當對象被放回對象池后,還保留著剛剛被客戶端調用時生成的數據。

臟對象可能帶來兩個問題

  • 臟對象持有上次使用的引用,導致內存泄漏等問題。
  • 臟對象如果下一次使用時沒有做清理,可能影響程序的處理數據。

生命周期的問題

處于對象池中的對象生命周期要比普通的對象要長久。維持大量的對象也是比較占用內存空間的。

以ThreadPoolExecutor為例,它提供了 allowCoreThreadTimeOut 和 setKeepAliveTime 兩種方法,可以在超時后銷毀核心線程。我們在具體的實踐中可以參考這個策略。

異常處理問題

相對來說,使用對象池client調用也會復雜一些,比如請求對象時有可能出現的阻塞,異常或者null值。這些都需要我們做一些額外的處理,來確保程序的正常運行。

除此之外,還有上面的提到的兩個問題,他們分別是

  • 同步問題
  • 池大小設置問題

所以當我們想要使用對象池時,需要謹慎的衡量并準確的實現,享受它帶來的好處,并避免其帶來的問題。

 

來自:http://droidyue.com/blog/2016/12/12/dive-into-object-pool/

 

 本文由用戶 KathieGrady 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!