Android中的數據庫連接池

在這裏插入圖片描述
最近在看數據庫相關的三方庫的時候,我發現在Android應用開發的時候是可以並行操作數據庫的讀寫,但Android默認的數據連接池中只有一個數據庫鏈接。一個數據庫連接能實現併發麼?要是一個數據庫鏈接可以實現併發,那麼爲什麼需要數據庫連接池?

數據庫連接池介紹

每次提到連接池我們很快能想到線程池。線程池的創建可以減少了創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個任務。

數據庫連接是一種關鍵的有限的昂貴的資源,對數據庫連接的管理能顯著影響到整個應用程序的伸縮性和健壯性,影響到程序的性能指標。數據庫連接池負責分配,管理和釋放數據庫連接,它允許應用程序重複使用一個現有的數據庫連接,減少鏈接不斷傳銷和銷燬帶來的資源浪費。

數據庫連接池在初始化時將創建一定數量的數據庫連接放到連接池中,,這些數據庫連接的數量是由最小數據庫連接數來設定的。無論這些數據庫連接是否被使用,連接池都將一直保證至少擁有這麼多的連接數量。連接池的最大數據庫連接數量限定了這個連接池能佔有的最大連接數,當應用程序向連接池請求的連接數超過最大連接數量時,這些請求將被加入到等待隊列中。

數據庫連接池的最小連接數和最大連接數的設置要考慮到以下幾個因素:

  1. 最小連接數:是連接池一直保持的數據庫連接,所以如果應用程序對數據庫連接的使用量不大,將會有大量的數據庫連接資源被浪費。

  2. 最大連接數:是連接池能申請的最大連接數,如果數據庫連接請求超過次數,後面的數據庫連接請求將被加入到等待隊列中,這會影響以後的數據庫操作

  3. 如果最小連接數與最大連接數相差很大:那麼最先連接請求將會獲利,之後超過最小連接數量的連接請求等價於建立一個新的數據庫連接。不過,這些大於最小連接數的數據庫連接在使用完不會馬上被釋放,他將被放到連接池中等待重複使用或是空間超時後被釋放。

Android數據庫相關類介紹

  • SQLiteOpenHelper:管理SQLite的幫助類,提供獲取SQLIteDatabase實例的方法,它會在第一次使用數據庫時調用獲取實例方法時創建SQLiteDatabase實例,並且處理數據庫版本變化,開發人員在實現ContentProvider時都要實現一個自定義的SQLiteOpenHelper類,處理數據的創建、升級和降級。
  • SQLiteDatabase:代表一個打開的SQLite數據庫,提供了執行數據庫操作的接口方法。如果不需要在進程之間共享數據,應用程序也可以自行創建這個類的實例來讀寫SQLite數據庫。
  • SQLiteSession:SQLiteSession負責管理數據庫連接和事務的生命週期,通過SQLiteConnectionPool獲取數據庫連接來執行具體的數據庫操作。
  • SQLiteConnectionPool:數據庫連接池,管理所有打開的數據庫連接(Connection)。所有數據庫連接都是通過它來打開,打開後會加入連接池,在讀寫數據庫時需要從連接池中獲取一個數據庫連接來使用。
  • SQLiteConnection:代表了數據庫連接,每個Connection封裝了一個native層的sqlite3實例,通過JNI調用SQLite動態庫的接口方法操作數據庫,Connection要麼被Session持有,要麼被連接池持有。
  • CursorFactory:可選的Cursor工廠,可以提供自定義工廠來創建Cursor。
  • DatabaseErrorHandler:可選的數據庫異常處理器(目前僅處理數據庫Corruption),如果不提供,將會使用默認的異常處理器。
  • SQLiteDatabaseConfiguration:數據庫配置,應用程序可以創建多個到SQLite數據庫的連接,這個類用來保證每個連接的配置都是相同的。
  • SQLiteQuery和SQLiteStatement:從抽象類SQLiteProgram派生,封裝了SQL語句的執行過程,在執行時自動組裝待執行的SQL語句,並調用SQLiteSession來執行數據庫操作。這兩個類的實現應用了設計模式中的命令模式。

每個類的更加詳細的介紹可以閱讀 SQLite數據庫學習小結**——Frameworks**層實現 這篇文章,我們這裏主要學習 SQLiteConnectionPool 相關的知識。

SQLiteConnectionPool

數據庫連接池我們先看一下它的大小,每個鏈接的獲取以及其他功能?

連接池大小

目前Android系統的實現中,如果以非WAL模式打開數據庫,連接池中只會保持一個數據庫連接,如果以WAL模式打開數據庫,連接池中的最大連接數量則根據系統配置決定,默認配置是兩個。

//SQLiteConnectionPool.java
private SQLiteConnectionPool(SQLiteDatabaseConfiguration configuration) {
    //數據庫的配置信息
    mConfiguration = new SQLiteDatabaseConfiguration(configuration);
    //設置最大的數據庫鏈接個數
    setMaxConnectionPoolSizeLocked();
    //超時處理句柄設置,如果超時時間爲MAX_VALUE,那麼鏈接永遠不關閉
    // If timeout is set, setup idle connection handler
    // In case of MAX_VALUE - idle connections are never closed
    if (mConfiguration.idleConnectionTimeoutMs != Long.MAX_VALUE) {
        setupIdleConnectionHandler(Looper.getMainLooper(),
                mConfiguration.idleConnectionTimeoutMs);
    }
}
private void setMaxConnectionPoolSizeLocked() {
    if (!mConfiguration.isInMemoryDb()
            && (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0){
        //獲取debug.sqlite.wal.poolsize配置的大小,默認值是com.android.internal.R.integer.db_connection_pool_size
        //最小值爲2
        mMaxConnectionPoolSize = SQLiteGlobal.getWALConnectionPoolSize();
    } else {
        // We don't actually need to always restrict the connection pool size to 1
        // for non-WAL databases.  There might be reasons to use connection pooling
        // with other journal modes. However, we should always keep pool size of 1 for in-memory
        // databases since every :memory: db is separate from another.
        // For now, enabling connection pooling and using WAL are the same thing in the API.
        //內存數據庫和非WAL數據庫時數據庫連接池大小爲1
        mMaxConnectionPoolSize = 1;
    }
}

雖然名爲連接池,但是從源碼來看,目前實現的池中只有一個數據庫連接(以後的Android版本可能會擴展),所以如果應用程序中有大量的併發數據庫讀和寫操作的話,每個操作的時長都可能受到影響,所以數據庫操作應放在工作線程中執行,以免影響UI響應。

這裏有人可能產生疑問,我在進行Android應用開發的時候是可以並行操作數據庫的讀寫,一個數據庫連接能實現併發麼?要是一個數據庫鏈接可以實現併發,那麼爲什麼需要數據庫連接池?

這裏說一下我自己的理解:一個數據庫鏈接是一個Socket 通道,當這個Connection 被其它 Session佔用的時候後續的Session 的操作必須等待這個 Connection 被釋放,所以數據庫的 Connection 的工作其實是串行的,這個在 MySqlOracle 的文檔中也能找到描述。所以在Android中默認的數據庫連接池只有一個數據庫鏈接的時候,所有在這個數據庫上的操作都是串行的。我們平時在多線程中的數據庫操作都是串行的。

這些將會下下面代碼分析的過程中一一體現出來_

數據庫鏈接池的構造

這裏主要講數據庫連接池的創建和池中的第一條鏈接的產生。

//SQLiteConnectionPool.java
public final class SQLiteConnectionPool implements Closeable {
    private static final String TAG = "SQLiteConnectionPool";
    // Amount of time to wait in milliseconds before unblocking acquireConnection
    // and logging a message about the connection pool being busy.
    private static final long CONNECTION_POOL_BUSY_MILLIS = 30 * 1000; // 30 seconds
    private final CloseGuard mCloseGuard = CloseGuard.get();
    private final Object mLock = new Object();
    private final AtomicBoolean mConnectionLeaked = new AtomicBoolean();
    //數據庫的配置信息
    private final SQLiteDatabaseConfiguration mConfiguration;
    //數據庫連接池的最大鏈接數
    private int mMaxConnectionPoolSize;
    //數據庫是否打開
    private boolean mIsOpen;
    //創建的鏈接id
    private int mNextConnectionId;
    //連接等待池其實是由等待的連接組成的鏈
    private ConnectionWaiter mConnectionWaiterPool;
    //連接等待隊列
    private ConnectionWaiter mConnectionWaiterQueue;
    //非主鏈接的引用,強引用需要主動回收
    // Strong references to all available connections.
    private final ArrayList<SQLiteConnection> mAvailableNonPrimaryConnections =
            new ArrayList<SQLiteConnection>();
    //主鏈接
    private SQLiteConnection mAvailablePrimaryConnection;
    /***部分代碼省略***/
}

打開數據庫

當我們在進行 SQLiteOpenHelper.getWritableDatabaseSQLiteOpenHelper.getReadableDatabase 的時候如果數據庫沒有打開那麼會打開數據庫,打開數據庫也就是創建數據庫鏈接。

//SQLiteDatabase.java
private static SQLiteDatabase openDatabase(@NonNull String path,
        @NonNull OpenParams openParams) {
    Preconditions.checkArgument(openParams != null, "OpenParams cannot be null");
    SQLiteDatabase db = new SQLiteDatabase(path, openParams.mOpenFlags,
            openParams.mCursorFactory, openParams.mErrorHandler,
            openParams.mLookasideSlotSize, openParams.mLookasideSlotCount,
            openParams.mIdleConnectionTimeout, openParams.mJournalMode, openParams.mSyncMode);
    //內部調用openInner
    db.open();
    return db;
}
//打開數據庫
private void openInner() {
    synchronized (mLock) {
        assert mConnectionPoolLocked == null;
        mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
        mCloseGuardLocked.open("close");
    }
    synchronized (sActiveDatabases) {
        sActiveDatabases.put(this, null);
    }
}

在這裏我們看到了SQLiteConnectionPoll 的調用,這裏由數據庫鏈接池創建數據庫鏈接從而打開數據庫。

//SQLiteConnectionPool.java
public static SQLiteConnectionPool open(SQLiteDatabaseConfiguration configuration) {
    //校驗數據庫信息
    if (configuration == null) {
        throw new IllegalArgumentException("configuration must not be null.");
    }
    // Create the pool.創建連接池
    SQLiteConnectionPool pool = new SQLiteConnectionPool(configuration);
    pool.open(); // might throw
    return pool;
}
// Might throw,打開數據庫
private void open() {
    // Open the primary connection.
    // This might throw if the database is corrupt.
    //獲取主鏈接並打開數據庫,如果數據庫損壞可能拋出異常
    mAvailablePrimaryConnection = openConnectionLocked(mConfiguration,
            true /*primaryConnection*/); // might throw
    // Mark it released so it can be closed after idle timeout
    //釋放當前鏈接,以便於被關閉或者被超時回收
    synchronized (mLock) {
        if (mIdleConnectionHandler != null) {
            mIdleConnectionHandler.connectionReleased(mAvailablePrimaryConnection);
        }
    }
    // Mark the pool as being open for business.
    mIsOpen = true;
    mCloseGuard.open("close");
}
// Might throw.
private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration,
    boolean primaryConnection) {
    //connectionId作爲鏈接id,每次新創建一個數據庫鏈接id自增1
    final int connectionId = mNextConnectionId++;
    return SQLiteConnection.open(this, configuration,connectionId, primaryConnection); // might throw
}
//SQLiteConnection.java
// Called by SQLiteConnectionPool only.
static SQLiteConnection open(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration,
    int connectionId, boolean primaryConnection) {
    SQLiteConnection connection = new SQLiteConnection(pool, configuration,connectionId, primaryConnection);
    try {
        //建立數據庫鏈接
        connection.open();
        return connection;
    } catch (SQLiteException ex) {
        connection.dispose(false);
        throw ex;
    }
}

在這裏我們能看到數據庫連接池第一條鏈接的創建是在打開數據庫的時候。

創建數據庫鏈接

除過在打開數據的時候創建數據庫鏈接,我們還會在一下情況下可能創建數據庫鏈接。

  • 創建數據庫,調用open
  • 重新加載數據庫配置,調用reconfigure
  • 創建主鏈接,調用tryAcquirePrimaryConnectionLocked
  • 創建非主鏈接,調用tryAcquireNonPrimaryConnectionLocked

這四種情況我們都可能會調用 SQLiteConnectionPool.openConnectionLocked 創建數據庫鏈接,其中創建數據庫重新加載數據庫配置都是創建的主鏈接

數據庫鏈接的使用

在這之前我們先回想 ConnectionSession 的概念:

連接(Connection):連接是從客戶端到ORACLE實例的一條物理路徑。連接可以在網絡上建立,或者在本機通過IPC機制建立。通常會在客戶端進程與一個專用服務器或一個調度器之間建立連接。

會話(Session) 是和連接(Connection)是同時建立的,兩者是對同一件事情不同層次的描述。簡單講,連接(Connection)是物理上的客戶端同服務器的通信鏈路,會話(Session)是邏輯上的用戶同服務器的通信交互。

我們一般往數據庫插入一條數據:

//創建數據庫的help
OpenHelper openHelper = new OpenHelper(getApplicationContext(), "demo", null, 1);
//打開數據庫標識寫操作
SQLiteDatabase writableDatabase = tanzhenxing.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put("TIME", System.currentTimeMillis());
//向SYSTEM_MSG表插入一條數據,TIME的當時間戳
writableDatabase.insert("SYSTEM_MSG", null, contentValues);

SQliteDatabase的內部調用:

//SQLiteDatabase.java
public long insert(String table, String nullColumnHack, ContentValues values) {
    try {
        //內部封裝SQLiteStatement,調用statement.executeInsert();
        return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
    } catch (SQLException e) {
        Log.e(TAG, "Error inserting " + values, e);
        return -1;
    }
}
//SQLiteStatement.java
public long executeInsert() {
    acquireReference();
    try {
        return getSession().executeForLastInsertedRowId(
                getSql(), getBindArgs(), getConnectionFlags(), null);
    } catch (SQLiteDatabaseCorruptException ex) {
        onCorruption();
        throw ex;
    } finally {
        releaseReference();
    }
}
//SQLiteStatement.java的父類SQLiteProgram的方法
protected final SQLiteSession getSession() {
    return mDatabase.getThreadSession();
}

在這裏我們能看到最終的執行是有 Session 進行操作的。

//SQLiteDatabase.java
// Thread-local for database sessions that belong to this database.
// Each thread has its own database session.
// INVARIANT: Immutable.
//屬於當前數據庫的會話,每個線程都有一會話,不可變。
private final ThreadLocal<SQLiteSession> mThreadSession = ThreadLocal
        .withInitial(this::createSession);
SQLiteSession createSession() {
    final SQLiteConnectionPool pool;
    synchronized (mLock) {
        throwIfNotOpenLocked();
        pool = mConnectionPoolLocked;
    }
    return new SQLiteSession(pool);
}
SQLiteSession getThreadSession() {
    return mThreadSession.get(); // initialValue() throws if database closed
}

好了扯了這麼久到底什麼時候使用 Connection 呢?

//SQLiteSession.java
public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags,
        CancellationSignal cancellationSignal) {
    //校驗sql
    if (sql == null) {
        throw new IllegalArgumentException("sql must not be null.");
    }
    //對某些SQL語句(例如“ BEGIN”," COMMIT”和“ ROLLBACK”)執行特殊的重新解釋,以確保事務狀態不變式爲保持。 
    if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
        return 0;
    }
    //獲取數據庫鏈接
    acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
    try {
        //使用數據庫鏈接進行數據庫操作
        return mConnection.executeForLastInsertedRowId(sql, bindArgs,
                cancellationSignal); // might throw
    } finally {
        //釋放數據庫鏈接
        releaseConnection(); // might throw
    }
}
//從數據庫連接池中獲取鏈接
private void acquireConnection(String sql, int connectionFlags,
        CancellationSignal cancellationSignal) {
    if (mConnection == null) {
        assert mConnectionUseCount == 0;
        mConnection = mConnectionPool.acquireConnection(sql, connectionFlags,
                cancellationSignal); // might throw
        mConnectionFlags = connectionFlags;
    }
    mConnectionUseCount += 1;
}

我們總結一下上述內容:我們進行數據庫操作的時候每次操作都使用的Session ,多個線程執行數據庫操作會有多個SessionSession 的內部操作調用的是ConnectionConnection 是從數據庫連接池中獲取的。

如果數據庫連接池有多個數據庫鏈接,那麼數據庫的殂謝操作可以併發,否則只能串行操作。

從連接池中獲取數據庫鏈接

//SQLiteConnectionPool.java
public SQLiteConnection acquireConnection(String sql, int connectionFlags,
        CancellationSignal cancellationSignal) {
    SQLiteConnection con = waitForConnection(sql, connectionFlags, cancellationSignal);
    synchronized (mLock) {
        if (mIdleConnectionHandler != null) {
            mIdleConnectionHandler.connectionAcquired(con);
        }
    }
    return con;
}

從上面的 waitForConnection 方法的名字我們可以猜測這個方法可能產生阻塞。

//SQLiteConnectionPool.java
// Might throw.
private SQLiteConnection waitForConnection(String sql, int connectionFlags,
        CancellationSignal cancellationSignal) {
    //是否需要主鏈接
    final boolean wantPrimaryConnection =
            (connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0;
    final ConnectionWaiter waiter;
    final int nonce;
    synchronized (mLock) {
        throwIfClosedLocked();//如果數據庫關閉,那麼拋出異常
        // Abort if canceled.
        //如果取消信號的回調不爲空,那麼執行回調檢測是否需要取消
        if (cancellationSignal != null) {
            cancellationSignal.throwIfCanceled();
        }
        // Try to acquire a connection.
        //嘗試獲得一個數據庫鏈接
        SQLiteConnection connection = null;
        //如果不需要主鏈接,那麼嘗試獲取非主鏈接
        if (!wantPrimaryConnection) {
            connection = tryAcquireNonPrimaryConnectionLocked(
                    sql, connectionFlags); // might throw
        }
        //如果獲取不到非鏈接,那麼嘗試獲取主鏈接
        if (connection == null) {
            connection = tryAcquirePrimaryConnectionLocked(connectionFlags); // might throw
        }
        if (connection != null) {
            return connection;
        }
        // No connections available.  Enqueue a waiter in priority order.
        //沒有可用的連接。按優先級排隊服務員。
        final int priority = getPriority(connectionFlags);
        final long startTime = SystemClock.uptimeMillis();
        //創建一個等待獲取鏈接的對象
        waiter = obtainConnectionWaiterLocked(Thread.currentThread(), startTime,
                priority, wantPrimaryConnection, sql, connectionFlags);
        ConnectionWaiter predecessor = null;
        ConnectionWaiter successor = mConnectionWaiterQueue;
        //按照優先級查找插入的位置
        while (successor != null) {
            if (priority > successor.mPriority) {
                waiter.mNext = successor;
                break;
            }
            predecessor = successor;
            successor = successor.mNext;
        }
        //插入等待隊列
        if (predecessor != null) {
            predecessor.mNext = waiter;
        } else {
            mConnectionWaiterQueue = waiter;
        }
        nonce = waiter.mNonce;
    }
    // Set up the cancellation listener.
    //設置取消監聽器,在等待的過程中如果取消等待那麼執行cancelConnectionWaiterLocked
    if (cancellationSignal != null) {
        cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
            @Override
            public void onCancel() {
                synchronized (mLock) {
                    if (waiter.mNonce == nonce) {
                        //從等待隊列中刪除這個節點數據
                        //給waiter添加OperationCanceledException異常信息
                        //喚醒waiter對應線程的阻塞
                        //調用wakeConnectionWaitersLocked判斷隊列其他waiter是否狀態有更新
                        cancelConnectionWaiterLocked(waiter);
                    }
                }
            }
        });
    }
    try {
        // Park the thread until a connection is assigned or the pool is closed.
        // Rethrow an exception from the wait, if we got one.
        //駐留線程,直到分配了連接或關閉了池。
        //如果有異常,則從等待中拋出異常。 
        long busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
        long nextBusyTimeoutTime = waiter.mStartTime + busyTimeoutMillis;
        for (;;) {
            // Detect and recover from connection leaks.
            //是否需要從泄露中進行恢復,之前被調用onConnectionLeaked
            if (mConnectionLeaked.compareAndSet(true, false)) {
                synchronized (mLock) {
                    //爲等待數據庫鏈接隊列進行鏈接賦值
                    wakeConnectionWaitersLocked();
                }
            }
            // Wait to be unparked (may already have happened), a timeout, or interruption.
            //阻塞busyTimeoutMillis毫秒,或者中間被執行LockSupport.unpark
            //被執行cancelConnectionWaiterLocked進行取消
            //或者被執行wakeConnectionWaitersLocked進行鏈接分配
            LockSupport.parkNanos(this, busyTimeoutMillis * 1000000L);
            // Clear the interrupted flag, just in case.
            Thread.interrupted();//重置當前線程的中斷狀態
            // Check whether we are done waiting yet.
            //檢查我們是否已經完成等待。
            synchronized (mLock) {
                throwIfClosedLocked();//如果數據庫關閉,那麼拋出異常
                final SQLiteConnection connection = waiter.mAssignedConnection;
                final RuntimeException ex = waiter.mException;
                //如果已經分配鏈接,或者發送異常
                if (connection != null || ex != null) {
                    recycleConnectionWaiterLocked(waiter);//回收waiter
                    if (connection != null) {//返回分配鏈接
                        return connection;
                    }
                    throw ex; // rethrow!重新拋出異常
                }

                final long now = SystemClock.uptimeMillis();
                if (now < nextBusyTimeoutTime) {
                    //parkNanos阻塞時間不夠busyTimeoutMillis毫秒,被執行LockSupport.unpark
                    busyTimeoutMillis = now - nextBusyTimeoutTime;
                } else {
                    //輸出日誌
                    logConnectionPoolBusyLocked(now - waiter.mStartTime, connectionFlags);
                    //重置下次阻塞時間
                    busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
                    nextBusyTimeoutTime = now + busyTimeoutMillis;
                }
            }
        }
    } finally {
        // Remove the cancellation listener.
        //有異常,或者獲取等到了分配的鏈接那麼解綁回調信息
        if (cancellationSignal != null) {
            cancellationSignal.setOnCancelListener(null);
        }
    }
}

這裏利用LockSupport.parkNanos 循環判斷是否獲得了數據庫鏈接否則繼續睡眠,直到這次操作被取消或者獲得數據庫鏈接。

主鏈接的獲取

//SQLiteConnectionPool.java
// Might throw.
@GuardedBy("mLock")
private SQLiteConnection tryAcquirePrimaryConnectionLocked(int connectionFlags) {
    // If the primary connection is available, acquire it now.
    //如果主要連接可用,請立即獲取。
    SQLiteConnection connection = mAvailablePrimaryConnection;
    if (connection != null) {
        mAvailablePrimaryConnection = null;
        finishAcquireConnectionLocked(connection, connectionFlags); // might throw
        return connection;
    }
    // Make sure that the primary connection actually exists and has just been acquired.
    //確保主要連接確實存在並且剛剛被獲取。
    for (SQLiteConnection acquiredConnection : mAcquiredConnections.keySet()) {
        if (acquiredConnection.isPrimaryConnection()) {
            return null;
        }
    }
    // Uhoh.  No primary connection!  Either this is the first time we asked
    // for it, or maybe it leaked?
    //第一次創建數據庫主鏈接,或者主鏈接被回收
    connection = openConnectionLocked(mConfiguration,
            true /*primaryConnection*/); // might throw
    finishAcquireConnectionLocked(connection, connectionFlags); // might throw
    return connection;
}

我們可以看到主數據庫鏈接只會有一個,如果被佔用那麼需要等待,如果沒有那麼就需要創建。

獲取非主鏈接

//SQLiteConnectionPool.java
// Might throw.
private SQLiteConnection tryAcquireNonPrimaryConnectionLocked(
        String sql, int connectionFlags) {
    // Try to acquire the next connection in the queue.
    SQLiteConnection connection;
    //嘗試獲取隊列中的下一個連接。
    final int availableCount = mAvailableNonPrimaryConnections.size();
    if (availableCount > 1 && sql != null) {
        // If we have a choice, then prefer a connection that has the
        // prepared statement in its cache.
        //檢查我們是否可以在其緩存中選擇具有prepare語句的連接。
        for (int i = 0; i < availableCount; i++) {
            connection = mAvailableNonPrimaryConnections.get(i);
            if (connection.isPreparedStatementInCache(sql)) {
                mAvailableNonPrimaryConnections.remove(i);
                finishAcquireConnectionLocked(connection, connectionFlags); // might throw
                return connection;
            }
        }
    }
    //否則獲取可以非用連接隊列中的最後一個鏈接
    if (availableCount > 0) {
        // Otherwise, just grab the next one.
        connection = mAvailableNonPrimaryConnections.remove(availableCount - 1);
        finishAcquireConnectionLocked(connection, connectionFlags); // might throw
        return connection;
    }
    //如果沒有可以的非主鏈接,那麼就需要擴展數據庫連接池
    // Expand the pool if needed.
    int openConnections = mAcquiredConnections.size();
    if (mAvailablePrimaryConnection != null) {
        openConnections += 1;
    }
    //如果數據庫連接池已經達到上限那麼,返回null
    if (openConnections >= mMaxConnectionPoolSize) {
        return null;
    }
    //否則創建新的非主鏈接
    connection = openConnectionLocked(mConfiguration,
            false /*primaryConnection*/); // might throw
    finishAcquireConnectionLocked(connection, connectionFlags); // might throw
    return connection;
}

非主數據庫鏈接數量的多少受限於數據庫連接池的大小。

數據庫鏈接釋放

有創建獲取就會有釋放回收。

//SQLiteConnectionPool.java
//釋放數據庫鏈接返回連接池
public void releaseConnection(SQLiteConnection connection) {
    synchronized (mLock) {
        //idle鏈接句柄事件處理connectionReleased
        if (mIdleConnectionHandler != null) {
            mIdleConnectionHandler.connectionReleased(connection);
        }
        //獲取這個鏈接的狀態
        //NORMAL,正常返回連接池
        //RECONFIGURE,必須先重新配置連接,然後才能返回。
        //DISCARD,連接必須關閉並丟棄。
        AcquiredConnectionStatus status = mAcquiredConnections.remove(connection);
        if (status == null) {
            throw new IllegalStateException("Cannot perform this operation "
                    + "because the specified connection was not acquired "
                    + "from this pool or has already been released.");
        }
        //簡歷是否已經關閉連接池
        if (!mIsOpen) {
            closeConnectionAndLogExceptionsLocked(connection);
        } else if (connection.isPrimaryConnection()) {//如果是主鏈接
            //判斷這個數據庫鏈接是否需要回收
            if (recycleConnectionLocked(connection, status)) {
                assert mAvailablePrimaryConnection == null;
                mAvailablePrimaryConnection = connection;//標識主鏈接可用,被佔用的時候爲null
            }
            ////判斷隊列其他waiter是否狀態有更新
            wakeConnectionWaitersLocked();
        } else if (mAvailableNonPrimaryConnections.size() >= mMaxConnectionPoolSize - 1) {
            //可用的非主鏈接數+主鏈接大於或等於數據庫連接池的最大鏈接數的時候關閉這個鏈接
            closeConnectionAndLogExceptionsLocked(connection);
        } else {
            //判斷這個數據庫鏈接是否需要回收
            if (recycleConnectionLocked(connection, status)) {
                //將這個鏈接添加到非主鏈接容器中
                mAvailableNonPrimaryConnections.add(connection);
            }
            //判斷隊列其他waiter是否狀態有更新
            wakeConnectionWaitersLocked();
        }
    }
}

//取消所有具有我們可以滿足的要求的waiter的park,即喚醒該waiter對應的線程
//這個方法並不會拋異常,而是將異常賦值給waiter進行拋出
// Can't throw.
@GuardedBy("mLock")
private void wakeConnectionWaitersLocked() {
    // Unpark all waiters that have requests that we can fulfill.
    // This method is designed to not throw runtime exceptions, although we might send
    // a waiter an exception for it to rethrow.
    ConnectionWaiter predecessor = null;
    //鏈表的頭結點
    ConnectionWaiter waiter = mConnectionWaiterQueue;
    boolean primaryConnectionNotAvailable = false;
    boolean nonPrimaryConnectionNotAvailable = false;
    while (waiter != null) {
        boolean unpark = false;
        //是否關閉了數據庫,如果關閉了那麼喚醒所有waiter的線程
        if (!mIsOpen) {
            unpark = true;
        } else {
            try {
                SQLiteConnection connection = null;
                //如果該waiter需要非主鏈接,而且現在有可用的非主鏈接
                if (!waiter.mWantPrimaryConnection && !nonPrimaryConnectionNotAvailable) {
                    //獲取非主鏈接
                    connection = tryAcquireNonPrimaryConnectionLocked(
                            waiter.mSql, waiter.mConnectionFlags); // might throw
                    //獲取爲空,標識現在沒有可用的非主鏈接        
                    if (connection == null) {
                        nonPrimaryConnectionNotAvailable = true;
                    }
                }
                //主鏈接可以用
                if (connection == null && !primaryConnectionNotAvailable) {
                    //嘗試獲取主鏈接
                    connection = tryAcquirePrimaryConnectionLocked(
                            waiter.mConnectionFlags); // might throw
                    //獲取爲空,標識現在主鏈接不可用
                    if (connection == null) {
                        primaryConnectionNotAvailable = true;
                    }
                }
                //獲取到了數據庫鏈接
                if (connection != null) {
                    waiter.mAssignedConnection = connection;//改waiter賦值鏈接
                    unpark = true;//喚醒該waiter的對應線程
                } else if (nonPrimaryConnectionNotAvailable && primaryConnectionNotAvailable) {
                    // There are no connections available and the pool is still open.
                    // We cannot fulfill any more connection requests, so stop here.
                    //連接池任然可用,但是沒有可用的鏈接沒法對其他的waiter狀態做更新直接返回
                    break;
                }
            } catch (RuntimeException ex) {
                // Let the waiter handle the exception from acquiring a connection.
                waiter.mException = ex;
                unpark = true;
            }
        }

        final ConnectionWaiter successor = waiter.mNext;
        //如果需要喚醒,那麼從鏈表中刪除這個waiter,並進行對應線程喚醒操作
        if (unpark) {
            if (predecessor != null) {
                predecessor.mNext = successor;
            } else {
                mConnectionWaiterQueue = successor;
            }
            waiter.mNext = null;
            LockSupport.unpark(waiter.mThread);
        } else {
            predecessor = waiter;
        }
        waiter = successor;
    }
}

鏈接的釋放有時候是爲了回收,有時候爲了重用。重用的時候還需要喚醒等待鏈接隊列中獲得這個鏈接的waiter

數據庫鏈接池的關閉

說到數據庫連接池的關閉,我們會聯想到數據庫的關閉數據庫鏈接的關閉

//SQLiteClosable.java,它是SQLiteDatabase的父類
/**
 * Releases a reference to the object, closing the object if the last reference
 * was released.
 *
 * Calling this method is equivalent to calling {@link #releaseReference}.
 *
 * @see #releaseReference()
 * @see #onAllReferencesReleased()
 */
//釋放引用的對象,直到所有的引用都被釋放了那麼關閉數據庫
public void close() {
    releaseReference();
}
public void releaseReference() {
    boolean refCountIsZero = false;
    synchronized(this) {
        refCountIsZero = --mReferenceCount == 0;
    }
    if (refCountIsZero) {
        onAllReferencesReleased();
    }
}
//SQLiteDatabase.java
@Override
protected void onAllReferencesReleased() {
    dispose(false);
}
private void dispose(boolean finalized) {
    final SQLiteConnectionPool pool;
    synchronized (mLock) {
        if (mCloseGuardLocked != null) {
            if (finalized) {
                mCloseGuardLocked.warnIfOpen();
            }
            mCloseGuardLocked.close();
        }
        //連接池置空,無法進行新操作
        pool = mConnectionPoolLocked;
        mConnectionPoolLocked = null;
    }
    if (!finalized) {
        //刪除當前數據庫的引用
        synchronized (sActiveDatabases) {
            sActiveDatabases.remove(this);
        }
        //關閉數據庫連接池
        if (pool != null) {
            pool.close();
        }
    }
}

我們可以看到數據庫連接池的關閉是由數據庫關閉引起的。

關閉數據庫連接池

//SQLiteConnectionPool.java
/**
 * Closes the connection pool.
 * <p>
 * When the connection pool is closed, it will refuse all further requests
 * to acquire connections.  All connections that are currently available in
 * the pool are closed immediately.  Any connections that are still in use
 * will be closed as soon as they are returned to the pool.
 * </p>
 *
 * @throws IllegalStateException if the pool has been closed.
 */
//關閉數據庫連接池,停止接受新的數據庫鏈接的請求。
//鏈接池中的可用鏈接立即被關閉,其他正在使用的鏈接被歸還到數據的時候關閉
public void close() {
    dispose(false);
}
private void dispose(boolean finalized) {
    if (mCloseGuard != null) {
        if (finalized) {
            mCloseGuard.warnIfOpen();
        }
        mCloseGuard.close();
    }
    if (!finalized) {
        // Close all connections.  We don't need (or want) to do this
        // when finalized because we don't know what state the connections
        // themselves will be in.  The finalizer is really just here for CloseGuard.
        // The connections will take care of themselves when their own finalizers run.
        synchronized (mLock) {
            throwIfClosedLocked();//檢測是否已經被關閉
            mIsOpen = false;//標識數據庫連接池關閉
            //關閉數據庫連接池中目前可用的鏈接(空閒的數據庫鏈接、包括空閒的主鏈接)
            closeAvailableConnectionsAndLogExceptionsLocked();
            final int pendingCount = mAcquiredConnections.size();
            //任然有鏈接正在使用中
            if (pendingCount != 0) {
                Log.i(TAG, "The connection pool for " + mConfiguration.label
                        + " has been closed but there are still "
                        + pendingCount + " connections in use.  They will be closed "
                        + "as they are released back to the pool.");
            }
            //判斷隊列其他waiter是否狀態有更新
            wakeConnectionWaitersLocked();
        }
    }
}

關閉數據庫鏈接

//SQLiteConnectionPool.java
// Can't throw.
@GuardedBy("mLock")
private void closeAvailableConnectionsAndLogExceptionsLocked() {
    //關閉可用的非主鏈接
    closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
    //如果主鏈接可用,那麼關閉主鏈接
    if (mAvailablePrimaryConnection != null) {
        closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
        mAvailablePrimaryConnection = null;
    }
}
// Can't throw.
@GuardedBy("mLock")
private void closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked() {
    //循環遍歷可用的非主鏈接,進行數據庫鏈接的關閉
    final int count = mAvailableNonPrimaryConnections.size();
    for (int i = 0; i < count; i++) {
        closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i));
    }
    mAvailableNonPrimaryConnections.clear();
}
// Can't throw.
@GuardedBy("mLock")
private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) {
    try {
        connection.close(); // might throw
        if (mIdleConnectionHandler != null) {
            mIdleConnectionHandler.connectionClosed(connection);
        }
    } catch (RuntimeException ex) {
        Log.e(TAG, "Failed to close connection, its fate is now in the hands "
                + "of the merciful GC: " + connection, ex);
    }
}
  • 數據庫關閉的時候引用次數自減,若引用次數歸零則真正執行關閉數據庫;
  • 數據庫關閉清楚引用後進行的是數據庫連接池的關閉;
  • 數據庫的關閉先狀態,然後關閉所有的空閒鏈接,使用中的連接回歸連接池後被關閉;

文章到這裏就全部講述完啦,若有其他需要交流的可以留言哦

想閱讀作者的更多文章,可以查看我 個人博客 和公共號:
振興書城

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章