Android SQLite3多線程操作問(wèn)題研究總結(jié)
最近做項(xiàng)目時(shí)在多線程讀寫(xiě)數(shù)據(jù)庫(kù)時(shí)拋出了異常,這自然是我對(duì)SQlite3有理解不到位的地方,所以事后仔細(xì)探究了一番。
1.關(guān)于getWriteableDataBase()和getReadableDatabase()的真正作用
getWriteableDataBase()其實(shí)是相當(dāng)于getReadableDatabase()的一個(gè)子方法,getWriteableDataBase()是只能返回一個(gè)以讀寫(xiě)方式打開(kāi)的SQLiteDatabase的引用,如果此時(shí)數(shù)據(jù)庫(kù)不可寫(xiě)時(shí)就會(huì)拋出異常,比如數(shù)據(jù)庫(kù)的磁盤(pán)空間滿了的情況。而getReadableDatabase()一般默認(rèn)是調(diào)用getWriteableDataBase()方法,如果數(shù)據(jù)庫(kù)不可寫(xiě)時(shí)就會(huì)返回一個(gè)以只讀方式打開(kāi)的SQLiteDatabase的引用,這就是二者最明顯的區(qū)別。
關(guān)鍵源碼如下:
public synchronized SQLiteDatabase getWritableDatabase() { if (mDatabase != null) { if (!mDatabase.isOpen()) { // darn! the user closed the database by calling mDatabase.close() mDatabase = null; } else if (!mDatabase.isReadOnly()) { return mDatabase; // The database is already open for business } } ... ... public synchronized SQLiteDatabase getReadableDatabase() { if (mDatabase != null) { if (!mDatabase.isOpen()) { // darn! the user closed the database by calling mDatabase.close() mDatabase = null; } else { return mDatabase; // The database is already open for business } } ... ... try { return getWritableDatabase(); } ... ...
2.SQLiteDatabase的同步鎖
其實(shí)在只使用一個(gè)SQLiteDatabase的引用時(shí),SQLiteDatabase對(duì)CRUD操作都會(huì)加上一個(gè)鎖(因?yàn)槭莇b文件,所以精確至數(shù)據(jù)庫(kù)級(jí)),這就保證了在同一時(shí)間你只能進(jìn)行一項(xiàng)操作,無(wú)論是不是在同一個(gè)線程中,這就導(dǎo)致了如果你在程序中對(duì)SQLiteOpenHelper使用了單例模式,那么你對(duì)數(shù)據(jù)庫(kù)讀寫(xiě)進(jìn)行任何的優(yōu)化操作都是"徒勞"。
3.多線程讀數(shù)據(jù)庫(kù)
仔細(xì)看源碼你會(huì)發(fā)現(xiàn),在數(shù)據(jù)庫(kù)操作中只有add,delete,update會(huì)調(diào)用lock(),而query()是不會(huì)調(diào)用的,但是在加載數(shù)據(jù)時(shí),調(diào)用了SQLiteQuery的fillWindow方法,而該方法依然會(huì)調(diào)用SQLiteDatabase.lock(),所以要想真正的實(shí)現(xiàn)多線程讀數(shù)據(jù)庫(kù),只能每個(gè)線程使用各自的SQLiteOpenHelper對(duì)象進(jìn)行讀操作,這樣就可避開(kāi)同步鎖。關(guān)鍵源碼如下:
/* package */ int fillWindow(CursorWindow window, int maxRead, int lastPos) { long timeStart = SystemClock.uptimeMillis(); mDatabase.lock(); mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX); try {... ...
4.多線程讀寫(xiě)
實(shí)現(xiàn)多線程讀寫(xiě)的關(guān)鍵是enableWriteAheadLogging屬性,這個(gè)方法 API Level 11添加的,也就是所3.0以上的版本就基本不可能實(shí)現(xiàn)真正的多線程讀寫(xiě)了。簡(jiǎn)單的說(shuō)通過(guò)調(diào)用enableWriteAheadLogging()和disableWriteAheadLogging()可以控制該數(shù)據(jù)是否被運(yùn)行多線程讀寫(xiě),如果允許,它將允許一個(gè)寫(xiě)線程與多個(gè)讀線程同時(shí)在一個(gè)SQLiteDatabase上起作用。實(shí)現(xiàn)原理是寫(xiě)操作其實(shí)是在一個(gè)單獨(dú)的log文件,讀操作讀的是原數(shù)據(jù)文件,是寫(xiě)操作開(kāi)始之前的內(nèi)容,從而互不影響。當(dāng)寫(xiě)操作結(jié)束后讀操作將察覺(jué)到新數(shù)據(jù)庫(kù)的狀態(tài)。當(dāng)然這樣做的弊端是將消耗更多的內(nèi)存空間。
5.多線程寫(xiě)
這個(gè)就不用多想了,SQLite壓根不支持,如果實(shí)在有需求可以使用多個(gè)數(shù)據(jù)庫(kù)文件。
6.備注
(1)你有沒(méi)有想SQLite最多支持多少個(gè)數(shù)據(jù)庫(kù)連接,其實(shí)在官方API文檔(enableWriteAheadLogging ()方法)中給出了最精確的答案:The maximum number of connections used to execute queries in parallel is dependent upon the device memory and possibly other properties.就是看你有多少內(nèi)存,但是我感覺(jué)這話說(shuō)的有點(diǎn)大,是不?哈哈。
(2)當(dāng)你在多線程中只使用一個(gè)SQLiteDatabase的引用時(shí),需要格外注意你SQLiteDataBase.close()調(diào)用的時(shí)機(jī),因?yàn)槟闶鞘褂玫耐粋€(gè)引用,比如在一個(gè)線程中當(dāng)一個(gè)Add操作結(jié)束后立刻關(guān)閉了數(shù)據(jù)庫(kù)連接,而另一個(gè)現(xiàn)場(chǎng)中正準(zhǔn)備執(zhí)行查詢操作,但此時(shí)db已經(jīng)被關(guān)閉了,然后就會(huì)報(bào)異常錯(cuò)誤。此時(shí)一般有三種解決方案,①簡(jiǎn)單粗暴給所有的CRUD添加一個(gè) synchronized關(guān)鍵字;②永遠(yuǎn)不關(guān)閉數(shù)據(jù)庫(kù)連接,只在最后退出是關(guān)閉連接。其實(shí)每次執(zhí)行g(shù)etWriteableDataBase()或getReadableDatabase()方法時(shí),如果有已經(jīng)建立的數(shù)據(jù)庫(kù)連接則直接返回(例外:如果舊的連接是以只讀方式打開(kāi)的,則會(huì)在新建連接成功的前提下,關(guān)閉舊連接),所以程序中將始終保持有且只有一個(gè)數(shù)據(jù)庫(kù)連接(前提是單例),資源消耗的很少。③可以自己進(jìn)行引用計(jì)數(shù),簡(jiǎn)單示例代碼如下:
//打開(kāi)數(shù)據(jù)庫(kù)方法 public synchronized SQLiteDatabase openDatabase() { if (mOpenCounter.incrementAndGet() == 1) { // Opening new database try { mDatabase = sInstance.getWritableDatabase(); } catch (Exception e) { mDatabase = sInstance.getReadableDatabase(); } } return mDatabase; } //關(guān)閉數(shù)據(jù)庫(kù)方法 public synchronized void closeDatabase() { if (mOpenCounter.decrementAndGet() == 0) { // Closing database mDatabase.close(); } }
(3)還有一些比較好的習(xí)慣和常識(shí),例如關(guān)閉Cursor,使用Transaction,SQLite存儲(chǔ)數(shù)據(jù)時(shí)其實(shí)不區(qū)分類型,以及SQLite支持大部分標(biāo)準(zhǔn)SQL語(yǔ)句,增刪改查語(yǔ)句都是通用的等等。
- android開(kāi)發(fā)教程之handle實(shí)現(xiàn)多線程和異步處理
- Android多線程及異步處理問(wèn)題詳細(xì)探討
- Handler與Android多線程詳解
- android中多線程下載實(shí)例
- Android中創(chuàng)建多線程管理器實(shí)例
- android 多線程技術(shù)應(yīng)用
- Android開(kāi)發(fā)筆記之:深入理解多線程AsyncTask
- Android多線程處理機(jī)制中的Handler使用介紹
- Android 中 EventBus 的使用之多線程事件處理
- Android實(shí)現(xiàn)多線程下載文件的方法
- Android多線程學(xué)習(xí)實(shí)例詳解
相關(guān)文章
Android webView如何輸出自定義網(wǎng)頁(yè)
這篇文章主要介紹了Android webView如何輸出自定義網(wǎng)頁(yè),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09Android自定義View仿大眾點(diǎn)評(píng)星星評(píng)分控件
這篇文章主要為大家詳細(xì)介紹了Android自定義View仿大眾點(diǎn)評(píng)星星評(píng)分控件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03簡(jiǎn)單好用的Adapter---ArrayAdapter詳解
這篇文章主要介紹了簡(jiǎn)單好用的Adapter---ArrayAdapter詳解,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11簡(jiǎn)述angular自定義過(guò)濾器在頁(yè)面和控制器中的使用
這篇文章主要介紹了簡(jiǎn)述angular自定義過(guò)濾器在頁(yè)面和控制器中的使用的相關(guān)資料,需要的朋友可以參考下2016-09-09Flutter自定義下拉刷新時(shí)的loading樣式的方法詳解
Flutter中的下拉刷新,我們通常RefreshIndicator,可以通過(guò)color或strokeWidth設(shè)置下拉刷新的顏色粗細(xì)等樣式,但如果要自定義自己的widget,RefreshIndicator并沒(méi)有暴露出對(duì)應(yīng)的屬性,那如何修改呢,文中給大家介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01Android開(kāi)發(fā)實(shí)現(xiàn)各種圖形繪制功能示例
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)各種圖形繪制功能,結(jié)合實(shí)例形式分析了Android圖形繪制常用的組件、函數(shù)使用方法及相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-09-09Android通過(guò)ExifInterface判斷Camera圖片方向的方法
今天小編就為大家分享一篇關(guān)于Android通過(guò)ExifInterface判斷相機(jī)圖片朝向的方法,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12Android App端與PHP Web端的簡(jiǎn)單數(shù)據(jù)交互實(shí)現(xiàn)示例
本篇文章主要介紹了Android App端與PHP Web端的簡(jiǎn)單數(shù)據(jù)交互實(shí)現(xiàn)示例,詳細(xì)的介紹了交互的代碼,非常具有實(shí)用價(jià)值,有興趣的可以了解一下2017-10-10c++ mk文件出錯(cuò)Jni調(diào)用產(chǎn)生java.lang.UnsatisfiedLinkError錯(cuò)誤解決方法
錯(cuò)誤產(chǎn)生在我把方法從c語(yǔ)言轉(zhuǎn)為c++語(yǔ)言后產(chǎn)生的,后來(lái)檢查到這種錯(cuò)誤是因?yàn)閙k文件出錯(cuò),加載c文件和加載c++的文件所用的代碼不一樣,下面請(qǐng)看2013-11-11