本篇主要關注SQLiteDatabase的線程同步實作與架構實作。
1 SQLiteClosable的acquireReference與releaseReference方法
SQLiteClosable是SQLiteDatabase的父類,也同時是資料庫下其他幾個類的父類。其中實作了引用計數邏輯來控制資源釋放的時機。
private int mReferenceCount = 1;
public void acquireReference() {
synchronized(this) {
if (mReferenceCount <= 0) {
throw new IllegalStateException(
"attempt to re-open an already-closed object: " + this);
}
mReferenceCount++;
}
}
public void releaseReference() {
boolean refCountIsZero = false;
synchronized(this) {
refCountIsZero = --mReferenceCount == 0;
}
if (refCountIsZero) {
onAllReferencesReleased();
}
}
可以看到這裡用mReferenceCount簡單地實作了一個引用計數。而引用計數的初始值是1。SQLiteDatabase會在每次操作前調用一次acquireReference,而在結束後調用一次releaseReference。為了友善,下文中把這樣的被acquireReference和releaseReference包裹的過程稱為一次“操作”。
那麼如果這兩個方法保持成對調用的話,是不是就不可能觸發onAllReferenceReleased方法?事實上,SQLiteClosable還有一個方法close調用了releaseReference。由于鎖的存在,隻要不在其它“操作”中調用close,調用close之後mReferenceCount的值可以斷定是0。
到這裡為止,感覺上是可以用一個boolean值來标記引用狀态的。因為由于鎖的存在,隻要各個“操作”是序列進行的(沒有一個“操作”調用了另一個“操作”的情況),mReferenceCount隻可能是0和1。推測引用計數就是為了應付“操作”之間存在調用這種情況。這就像同一個線程裡的嵌套鎖需要進行計數一樣。
2 SQLiteDatabase的打開與關閉
2.1 關閉
上文中提到的onAllReferenceReleased是一個抽象方法。其在SQLiteDatabase中的實作為
@Override
protected void onAllReferencesReleased() {
dispose(false);
}
在finalize中同樣調用了dispose方法
protected void finalize() throws Throwable {
try {
dispose(true);
} finally {
super.finalize();
}
}
而dispose的實作為
private void dispose(boolean finalized) {
final SQLiteConnectionPool pool;
synchronized (mLock) {
if (mCloseGuardLocked != null) {
if (finalized) {
//CloseGuard是一個監測是否及時調用close方法的類,一般來說除了輸出日志并不會做别的什麼
//這裡事實上就是在finalize的時候如果沒有close過,就輸出一條日志
mCloseGuardLocked.warnIfOpen();
}
mCloseGuardLocked.close();
}
pool = mConnectionPoolLocked;//這個mConnectionPool是連接配接池。此方法裡将其置空并關閉。後文詳細讨論其作用。
mConnectionPoolLocked = null;
}
if (!finalized) {
//sActiveDatabases是一個靜态的WeakHashMap,用key來放置所有活動資料庫,而value并沒有作用。dispose的時候自然要移除this。
//跟蹤代碼分析下來,用這個map隻是為了bug report
synchronized (sActiveDatabases) {
sActiveDatabases.remove(this);
}
if (pool != null) {
pool.close();
}
}
}
2.2 打開
在本系列第一篇中我們曾看到過,最終的打開資料庫的是一個靜态方法,SQLiteDatabase.openDatabase(String path, CursorFactory factory, int flags, DatabaseErrorHandler errorHandler)。
public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
DatabaseErrorHandler errorHandler) {
SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler);
db.open();
return db;
}
這裡很簡單,就是建立一個對象,然後調用open。構造器裡隻有一些初始化,略過。着重看open方法:
private void open() {
try {
try {
openInner();//嘗試一次
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();//失敗了,再次嘗試前調用另一個方法。
openInner();
}
} catch (SQLiteException ex) {
Log.e(TAG, "Failed to open database '" + getLabel() + "'.", ex);
close();
throw ex;
}
}
private void openInner() {
synchronized (mLock) {
assert mConnectionPoolLocked == null;
mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
mCloseGuardLocked.open("close");
}
synchronized (sActiveDatabases) {//這是之前那個WeakHashMap
sActiveDatabases.put(this, null);
}
}
void onCorruption() {
EventLog.writeEvent(EVENT_DB_CORRUPT, getLabel());
mErrorHandler.onCorruption(this);
}
open中會嘗試調用openInner。如果失敗一次,則調用onCorruption,随後再嘗試一次。mErrorHandler是構造器傳入的,構造器參數由靜态方法openDatabase傳入,而這個參數又最終從SQLiteOpenHelper傳入。
openInner中做的事情,從命名上看,是開啟一個SQLiteConnectionPool即資料庫連接配接池。簡單地說,資料庫連接配接池維持了對資料庫的多個連接配接。資料庫連接配接的類是SQLiteConnection。
3 線程内單例的SQLiteSession
private final ThreadLocal mThreadSession = new ThreadLocal() {
@Override
protected SQLiteSession initialValue() {
return createSession();
}
};
SQLiteSession getThreadSession() {
return mThreadSession.get(); // initialValue() throws if database closed
}
SQLiteSession createSession() {
final SQLiteConnectionPool pool;
synchronized (mLock) {
throwIfNotOpenLocked();
pool = mConnectionPoolLocked;
}
return new SQLiteSession(pool);
}
ThreadLocal會在每個線程内維護一個對象,而線上程結束時解除對對象的引用。initialValue方法會線上程中不存在已有對象時建立一個,不Override的話會給出一個null。除此之外也可以通過ThreadLocal.set來給本線程配置一個對象。
可以看到mThreadSession是一個ThreadLocal。調用getThreadSession會擷取一個線程内單例的SQLiteSession對象。
SQLiteSession是提供資料庫操作能力(增删改查以及事務)的一個單元。它會從SQLiteConnectionPool即連接配接池中擷取連接配接,最終對資料庫進行操作。
到這兒類已經有點多了。整理一下邏輯:
(1)SQLiteDatabase持有一個ThreadLocal,用于對每個線程生成一個SQLiteSession;
(2)SQLiteSession持有SQLiteConnectionPool(雖然SQLiteDatabase也持有連接配接池對象,但它隻用來傳遞給SQLiteSession),但是同一個SQLiteDatabase下的SQLiteSession是共用一個SQLiteConnectionPool的;
(3)SQLiteConnectionPool管理SQLiteConnection并适時向SQLiteSession提供之;
(4)SQLiteConnection直接對底層資料庫進行操作(這個類裡面才有大量的native方法)。
接下來分析一下SQLiteSession。
擷取與釋放連接配接,還是一個引用計數實作:
private final SQLiteConnectionPool mConnectionPool;//構造器中初始化,值從SQLiteDatabase對象中傳入
private SQLiteConnection mConnection;
private int mConnectionFlags;
private int mConnectionUseCount;//無處不在的引用計數
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;
}
private void releaseConnection() {
assert mConnection != null;
assert mConnectionUseCount > 0;
if (--mConnectionUseCount == 0) {
try {
mConnectionPool.releaseConnection(mConnection); // might throw
} finally {
mConnection = null;
}
}
}
具體的資料庫操作有很多executeXXX形式的方法,邏輯大同小異。挑一個看看:
public int executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {//排除特殊操作
return 0;
}
//擷取連接配接
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
//底層資料庫操作。本文不關心。
return mConnection.executeForChangedRowCount(sql, bindArgs,
cancellationSignal); // might throw
} finally {
//釋放連接配接
releaseConnection(); // might throw
}
}
//用來支援'BEGIN','COMMIT','ROLLBACK'的操作。就是與Transaction相關的操作。
private boolean executeSpecial(String sql, Object[] bindArgs, int connectionFlags,
CancellationSignal cancellationSignal) {
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
}
final int type = DatabaseUtils.getSqlStatementType(sql);
switch (type) {
case DatabaseUtils.STATEMENT_BEGIN:
beginTransaction(TRANSACTION_MODE_EXCLUSIVE, null, connectionFlags,
cancellationSignal);
return true;
case DatabaseUtils.STATEMENT_COMMIT:
setTransactionSuccessful();
endTransaction(cancellationSignal);
return true;
case DatabaseUtils.STATEMENT_ABORT:
endTransaction(cancellationSignal);
return true;
}
return false;
}
4 單次完整的SQLite操作
4.1 SQLiteStatement
以最簡單的delete方法為例。其它方法的流程均大同小異。
public int delete(String table, String whereClause, String[] whereArgs) {
acquireReference();
try {
SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table +
(!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
try {
return statement.executeUpdateDelete();
} finally {
statement.close();
}
} finally {
releaseReference();
}
}
先用SQLiteStatement做一些sql轉義和拼接,然後調用statement.executeUpdateDelete()。
具體看一下executeUpdateDelete:
//以下來自SQLiteStatement
public int executeUpdateDelete() {
acquireReference();//注意這裡是SQLiteStatement内的引用計數,不是SQLiteDatabase了。
try {
return getSession().executeForChangedRowCount(
getSql(), getBindArgs(), getConnectionFlags(), null);//上一節分析過了,執行SQL。
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
releaseReference();
}
}
//這個方法在父類SQLiteProgram中。又回到了上一小節的getThreadSession。擷取線程内的單例。
protected final SQLiteSession getSession() {
return mDatabase.getThreadSession();
}
4.2 SQLiteDirectCursorDriver與SQLiteQuery
與4.1不同的是,在進行query操作時,最終沒有使用SQLiteStatement類,而是通過SQLiteDirectCursorDriver間接使用了SQLiteQuery。而SQLiteQuery與SQLiteStatement同為SQLiteProgram的子類,完成類似的功能。
所有的query操作最終均調用這樣一個方法:
public Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
String editTable, CancellationSignal cancellationSignal) {
acquireReference();
try {
SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable,
cancellationSignal);
return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory,
selectionArgs);
} finally {
releaseReference();
}
}
而SQLiteDirectCursorDriver的query方法如下:
public Cursor query(CursorFactory factory, String[] selectionArgs) {
final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal);
final Cursor cursor;
try {
query.bindAllArgsAsStrings(selectionArgs);
if (factory == null) {
cursor = new SQLiteCursor(this, mEditTable, query);
} else {
cursor = factory.newCursor(mDatabase, this, mEditTable, query);
}
} catch (RuntimeException ex) {
query.close();
throw ex;
}
mQuery = query;
return cursor;
}
其中建立了一個SQLiteQuery,并綁定參數。随後建立一個Cursor,這就是最終傳回的Cursor對象。接下來考察無CursorFactory情況下預設傳回的SQLiteCursor。
AbstractCursor中各種move方法均會調用moveToPosition,而moveToPosition會調用onMove,SQliteCursor中onMove的實作為:
@Override
public boolean onMove(int oldPosition, int newPosition) {
// Make sure the row at newPosition is present in the window
if (mWindow == null || newPosition < mWindow.getStartPosition() ||
newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
fillWindow(newPosition);
}
return true;
}
private void fillWindow(int requiredPos) {
clearOrCreateWindow(getDatabase().getPath());
try {
if (mCount == NO_COUNT) {
int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
mCursorWindowCapacity = mWindow.getNumRows();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
}
} else {
int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
mCursorWindowCapacity);
mQuery.fillWindow(mWindow, startPos, requiredPos, false);
}
} catch (RuntimeException ex) {
// Close the cursor window if the query failed and therefore will
// not produce any results. This helps to avoid accidentally leaking
// the cursor window if the client does not correctly handle exceptions
// and fails to close the cursor.
closeWindow();
throw ex;
}
}
核心邏輯在mQuery.fillWindow(mWindow, startPos, requiredPos, false);這裡。mQuery就是之前傳入的SQLiteQuery對象。檢視其fillWindow方法:
int fillWindow(CursorWindow window, int startPos, int requiredPos, boolean countAllRows) {
acquireReference();
try {
window.acquireReference();
try {
int numRows = getSession().executeForCursorWindow(getSql(), getBindArgs(),
window, startPos, requiredPos, countAllRows, getConnectionFlags(),
mCancellationSignal);
return numRows;
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} catch (SQLiteException ex) {
Log.e(TAG, "exception: " + ex.getMessage() + "; query: " + getSql());
throw ex;
} finally {
window.releaseReference();
}
} finally {
releaseReference();
}
}
可以看到,最終回到了SQLiteSession.executeXXX方法邏輯之下。其餘即與上一節類似。
而從Cursor中取出資料的過程,則最終是由CursorWindow下的一系列native方法來完成,我認為屬于Cursor的代碼體系了,這裡不重點展開。
5 Transaction
5.1 beginTransaction
//一群差不多的beginTransaction方法最終調用到了這裡
private void beginTransaction(SQLiteTransactionListener transactionListener,
boolean exclusive) {
acquireReference();//怎麼老是你
try {
getThreadSession().beginTransaction(
exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE :
SQLiteSession.TRANSACTION_MODE_IMMEDIATE,
transactionListener,
getThreadDefaultConnectionFlags(false ), null);
} finally {
releaseReference();
}
}
//上面的方法調用了這個方法。這套flags做了兩件小事:1.确定隻讀還是可寫 2.如果是主線程,就要提高連接配接的優先級
int getThreadDefaultConnectionFlags(boolean readOnly) {
int flags = readOnly ? SQLiteConnectionPool.CONNECTION_FLAG_READ_ONLY :
SQLiteConnectionPool.CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY;
if (isMainThread()) {
flags |= SQLiteConnectionPool.CONNECTION_FLAG_INTERACTIVE;
}
return flags;
}
還是要看SQLiteSession内部:
public void beginTransaction(int transactionMode,
SQLiteTransactionListener transactionListener, int connectionFlags,
CancellationSignal cancellationSignal) {
throwIfTransactionMarkedSuccessful();//一點合法性檢查,不貼了
beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags,
cancellationSignal);
}
private void beginTransactionUnchecked(int transactionMode,
SQLiteTransactionListener transactionListener, int connectionFlags,
CancellationSignal cancellationSignal) {
if (cancellationSignal != null) {
//cancellationSignal從beginTransaction以及SQLiteStatement諸方法傳入的均為null,調查發現僅query時可以傳入此參數。
cancellationSignal.throwIfCanceled();
}
if (mTransactionStack == null) {//Transaction棧為空時才擷取連接配接。
acquireConnection(null, connectionFlags, cancellationSignal); // might throw
}
try {
// Set up the transaction such that we can back out safely
// in case we fail part way.
if (mTransactionStack == null) {//如果沒有進行中的Transaction,建立一個并BEGIN
// Execute SQL might throw a runtime exception.
switch (transactionMode) {
case TRANSACTION_MODE_IMMEDIATE:
mConnection.execute("BEGIN IMMEDIATE;", null,
cancellationSignal); // might throw
break;
case TRANSACTION_MODE_EXCLUSIVE:
mConnection.execute("BEGIN EXCLUSIVE;", null,
cancellationSignal); // might throw
break;
default:
mConnection.execute("BEGIN;", null, cancellationSignal); // might throw
break;
}
}
// Listener might throw a runtime exception.
if (transactionListener != null) {
try {
transactionListener.onBegin(); // might throw
} catch (RuntimeException ex) {
if (mTransactionStack == null) {
mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
}
throw ex;
}
}
// Bookkeeping can't throw, except an OOM, which is just too bad...
Transaction transaction = obtainTransaction(transactionMode, transactionListener);//建立事務
transaction.mParent = mTransactionStack;
mTransactionStack = transaction;//入棧
} finally {
if (mTransactionStack == null) {//這裡要棧為空時才釋放連接配接。不為空時永遠持有一個連接配接。
releaseConnection(); // might throw
}
}
}
private static final class Transaction {
public Transaction mParent;//這個是個連結清單,或者說在這裡充當了一個棧
public int mMode;
public SQLiteTransactionListener mListener;
public boolean mMarkedSuccessful;
public boolean mChildFailed;
}
5.2 setTransactionSuccessful與endTransaction
直接看SQLiteSession吧:
public void setTransactionSuccessful() {
throwIfNoTransaction();
throwIfTransactionMarkedSuccessful();
mTransactionStack.mMarkedSuccessful = true;//僅僅是個标記
}
public void endTransaction(CancellationSignal cancellationSignal) {
throwIfNoTransaction();
assert mConnection != null;
endTransactionUnchecked(cancellationSignal, false);
}
private void endTransactionUnchecked(CancellationSignal cancellationSignal, boolean yielding) {
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
}
final Transaction top = mTransactionStack;
boolean successful = (top.mMarkedSuccessful || yielding) && !top.mChildFailed;//如果有子Transaction失敗,也是失敗的
RuntimeException listenerException = null;
final SQLiteTransactionListener listener = top.mListener;
if (listener != null) {
try {
if (successful) {
listener.onCommit(); // might throw
} else {
listener.onRollback(); // might throw
}
} catch (RuntimeException ex) {
listenerException = ex;
successful = false;
}
}
mTransactionStack = top.mParent;//退棧
recycleTransaction(top);//回收
if (mTransactionStack != null) {//還沒到最外層事務,隻做個标記
if (!successful) {
mTransactionStack.mChildFailed = true;
}
} else {//到了最外層事務了,送出或復原
try {
if (successful) {
mConnection.execute("COMMIT;", null, cancellationSignal); // might throw
} else {
mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
}
} finally {
releaseConnection(); // might throw
}
}
if (listenerException != null) {
throw listenerException;
}
}
6 總結
(1)總的來說,SQLiteDatabase是線程安全且高效的。它并沒有簡單地對每次操作加鎖,而是使用引用計數和ThreadLocal來保證連接配接複用的線程安全性,資料一緻性則交由SQLite自身去保證,以達到最優性能。
而很多時候我們在業務層封裝時反而處處加鎖,其實是沒有必要的。
(2)SQLiteDatabase的内部實作會讓每個線程單獨持有一個資料庫連接配接(不一定是建立,因為有連接配接池優化),而不是每個SQLiteDatabase對象對應一個連接配接。
(3)資料庫會給主線程持有的連接配接提高優先級。如果執行的是讀操作或者小量資料的寫入操作的話,可能可以滿足主線程低延遲的需要。但是還沒有具體的資料來支撐這一結論,希望有大牛補充。
(4)多線程下的事務行為本文中未作分析,下一篇會就此問題單獨進行讨論。