1. 程式人生 > >關於Android SQLite3多執行緒併發問題,學習筆記。

關於Android SQLite3多執行緒併發問題,學習筆記。

最近有看到過Sqlite3的相關文章,在這做一下學習筆記。

sqlite3資料庫是一個數據庫一個檔案,所以當多程序訪問操作同一資料庫時,即與操作同一檔案一樣,檔案鎖問題。
對同個資料庫進行多程序同時讀是允許的,但多程序同時寫是不允許的,如果一個程序已經正在寫,其他程序就會寫失敗。sqlite3返回資訊就是"Database is locked",錯誤碼SQLITE_BUSY,sqlite3對高併發訪問支援不好,多執行緒訪問資料庫會出現資料庫鎖定現象。

那麼在併發的情況下,如何安全的訪問sqlite資料庫呢?

假設你已經有一個自己的SQLiteOpenHelper。

public class DatabaseHelper extends SQLiteOpenHelper { ... }

//Thread 1
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();

// Thread 2
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();
在你的logcat中將會得到下面的輸出資訊,並且有一個寫操作將不會成功
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)

發生這種情況是因為你每次都建立了一個新的SQLiteOpenHelper,實際上你每次都建立了一個數據庫的連線。如果你在同一時間用不同的資料庫連線來對同一的資料庫進行寫操作的話,那麼其中一個會失敗。
Android平臺上,如果你想在多執行緒環境下安全的使用資料庫的話,那麼你得確保所有的執行緒使用的都是同一個資料庫連線。

確保只有一個數據庫連線存在,我們可以使用單例模式,程式碼如下:

public class DatabaseManager {

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;

    public static synchronized void initialize(Context context, SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        }
    }

    public static synchronized DatabaseManager getInstance() {
        if (instance == null) {
            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                    " is not initialized, call initialize(..) method first.");
        }

        return instance;
    }

    public synchronized SQLiteDatabase getDatabase() {
        return new mDatabaseHelper.getWritableDatabase();
    }

}

現在我們來看看執行結果:
// In your application class
DatabaseManager.initializeInstance(getApplicationContext());

// Thread 1
DatabaseManager manager = DatabaseManager.getInstance();
SQLiteDatabase database = manager.getDatabase()
database.insert(…);
database.close();

// Thread 2
DatabaseManager manager = DatabaseManager.getInstance();
SQLiteDatabase database = manager.getDatabase()
database.insert(…);
database.close();
你的應用將會崩潰,異常資訊如下:
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase

因為我們只使用一個數據庫連線,Thread1和Thread2的都是由getDatabase()方法返回的相同連線。發生的什麼事呢,在Thread2還在使用資料庫連線時,Thread1可能已經把它給關閉了,那就是為什麼你會得到崩潰異常。
我們需要確保在沒有任何一個人在使用資料庫時,才去關閉它。在StackOverflow上推薦的做法是永遠不要關閉資料庫。Android會尊重你這種做法,但會給你如下的提示。所以我一點也不推薦這種做法。

Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
下面是一個樣例程式
public class DatabaseManager{

    private AtomicInteger mOpenCounter = new AtomicInteger();

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;
    private SQLiteDatabase mDatabase;

    public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        }
    }

    public static synchronized DatabaseManager getInstance() {
        if (instance == null) {
            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                    " is not initialized, call initializeInstance(..) method first.");
        }

        return instance;
    }

    public synchronized SQLiteDatabase openDatabase() {
        if(mOpenCounter.incrementAndGet() == 1) {
            // Opening new database
            mDatabase = mDatabaseHelper.getWritableDatabase();
        }
        return mDatabase;
    }

    public synchronized void closeDatabase() {
        if(mOpenCounter.decrementAndGet() == 0) {
            // Closing database
            mDatabase.close();

        }
    }}
使用方式:
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way
每當你需要使用資料庫時,你需要使用DatabaseManager的openDatabase()方法來取得資料庫。我們會使用一個引用計數來判斷是否要建立資料庫物件。如果引用計數為1,則需要建立一個數據庫,如果不為1,說明我們已經建立過了。
在closeDatabase()方法中我們同樣通過判斷引用計數的值,如果引用計數降為0,則說明我們需要close資料庫。

大致的做法就是在多執行緒訪問的情況下需要自己來封裝一個DatabaseManager來管理Sqlite資料庫的讀寫,需要同步的同步,需要非同步的非同步,不要直接操作資料庫,這樣很容易出現因為鎖的問題導致加鎖後的操作失敗。

相關推薦

關於Android SQLite3執行併發問題學習筆記

最近有看到過Sqlite3的相關文章,在這做一下學習筆記。 sqlite3資料庫是一個數據庫一個檔案,所以當多程序訪問操作同一資料庫時,即與操作同一檔案一樣,檔案鎖問題。 對同個資料庫進行多程序同時讀是允許的,但多程序同時寫是不允許的,如果一個程序已經正在寫,其他程序就會

執行併發學習總結(一)Lock鎖

為什麼需要Lock鎖 1.因為我們需要有一種機制可以不讓等待的執行緒一直無期限地等待下去(比如只等待一定的時間或者能夠響應中斷),通過Lock就可以辦到。 2.通過Lock可以知道執行緒有沒有成功獲取到鎖 3.Lock鎖相當於汽車中的手動擋,相比synchron

關於android解決執行併發的問題

1.建立HandlerThread物件和Handler物件,併為HandlerThread生成的執行緒命名, private HandlerThread thread=new HandlerThread("handlerthread"); //建立的HandlerThrea

OkHttp實現執行併發下載的筆記

         打個廣告,不瞭解OkHttp的話可以先看下  http://blog.csdn.net/brycegao321/article/details/51830525             需求:  手機拍攝若干張照片, 在wifi連線下上傳到伺服器。  

block 知識點 ---- Objective-C 高階程式設計 iOS 與 OS X 執行記憶體管理 學習筆記

1. block捕捉變數: 結論:只有呼叫_Block_copy 才能持有截獲的附有 __strong 修飾符的物件型別的自動變數值。 block 中使用物件型別的自動變數時,除以下情形,推薦使用copy方法: “When the Block is returned

JAVA學習筆記併發程式設計 - 玖)- 執行併發拓展

文章目錄 死鎖 概念 產生條件 例子 併發最佳實踐 Spring與執行緒安全 死鎖 概念 死鎖是指兩個或兩個以上的程序在執行過程中,由於競爭資源或者由於彼此通訊而造成的一種阻塞的現象

乾貨!執行池+CountDownLatch實現 執行併發計算、彙總

目錄結構 抽象類:求和器 單執行緒 求和器 VS 多執行緒 求和器 1)執行緒池 多個執行緒 一起併發執行,效能很生猛 2)CountDownLatch 主執行緒 使用 latch.await() 阻塞住,直到所有 子任務 都執行完畢了

Java高併發——執行協作同步控制

      繼上一篇:Java高併發——多執行緒基礎 中講到,共享資源的合理使用,才能夠使多執行緒程式有條不紊的執行。其中我們通過synchronized來實現臨界區資源的是否可以訪問。而,這篇我們來重點總結synchronized的增強替代版鎖,以及其它JD

嵌入式Linux網路程式設計TCP併發伺服器TCP執行併發伺服器TCP程序併發伺服器

文章目錄 1,TCP多執行緒併發伺服器 1.1,標頭檔案net.h 1.2,客戶端client.c 1.3,伺服器端server.c 2,TCP多程序併發伺服器 2.1,標頭檔案net.h 2.2,客

Android執行-----併發和同步(volatile)

volatile是Java提供的一種輕量級的同步機制,在併發程式設計中,它也扮演著比較重要的角色。同synchronized相比(synchronized通常稱為重量級鎖),volatile更輕量級,相比使用synchronized所帶來的龐大開銷,倘若能恰當的合理的使用volatile,自然是好事

Android執行-----併發和同步(Lock)

一、為什麼需要Lock 如果一個程式碼塊被synchronized修飾了,當一個執行緒獲取了對應的鎖,並執行該程式碼塊時,其他執行緒便只能一直等待,等待獲取鎖的執行緒釋放鎖,而這裡獲取鎖的執行緒釋放鎖只會有兩種情況: 1)獲取鎖的執行緒執行完了該程式碼塊,然後執行緒釋放對鎖的佔有; 2)執行緒執

Android執行-----併發和同步(synchronized)

一、鎖 物件的內建鎖和物件的狀態之間是沒有內在的關聯的,雖然大多數類都將內建鎖用做一種有效的加鎖機制,但物件的域並不一定通過內建鎖來保護。當獲取到與物件關聯的內建鎖時,並不能阻止其他執行緒訪問該物件,當某個執行緒獲得物件的鎖之後,只能阻止其他執行緒獲得同一個鎖。之所以每個物件都有一個內建鎖,是為

Android執行-----併發和同步(ThreadLocal)

一.對ThreadLocal的理解        很多地方叫做執行緒本地變數,也有些地方叫做執行緒本地儲存,其實意思差不多。可能很多朋友都知道ThreadLocal為變數在每個執行緒中都建立了一個副本,那麼每個執行緒可以訪問自己內部的副本變數,也就是進行資

Android實現執行下載檔案支援斷點

本篇部落格主要介紹多執行緒去下載檔案,以下載多個app為例。不管去下在app,音視訊等檔案,實現起來都一樣。篇幅有點長,先貼張美女圖看看 正在下載的效果圖 下載完成效果圖 小編的下載路徑是放在sd卡的絕對路徑中,方便驗證! 工程目錄圖 介紹下每

java 併發程式設計學習筆記(九)執行併發拓展

                                         多執行緒

Android執行-----併發和同步(原子變數)

在java中的變數在++等操作是不是原子操作,分為先加一,然後賦值,從而在多執行緒編碼時需要加上synchronizeed,為了增加易用性,java當前提供了原子變數,當前的原子變數有AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference等,其特

springboot動態配置定時任務2種方式整合Quartz執行併發執行個定時任務配置

我的專案是採用的idea+gradle+springboot專案構建,下面是springboot實現定時任務所需要的jar包 //定時任務使用 compile group: 'org.quartz-scheduler', name: 'quartz', version:

Android 執行池模擬執行併發下載任務

廢話不多,直接上原始碼 自定義一個Adapter public class MyAdapter extends BaseAdapter { private Context context; private List<Progress> list

Java8學習計劃--關於執行併發程式設計-Java8-CompletableFuture 1的介紹

零零散散接近一個月的課餘時間,學完Java8InAction和Guava,感觸很多,收穫也很大,特別開心,接下來會利用空餘時間學習Spark,希望自己在技術上慢慢積累,越來越從容。對於Java8 最大的改變是lambda表示式 Collecotors CompletableF

Android 資料庫綜述(二) 程式計算器與訊號量來處理執行併發問題

Android 資料庫綜述(二) 程式計算器與訊號量來處理多執行緒併發問題 多執行緒操作資料庫,為處理併發問題,大家第一想到的是加鎖操作 ,SQLite是檔案級別的鎖.SQLite3對於併發的處理機制是允許同一個程序的多個執行緒同時讀取一個數據庫,但是任何時刻只允許一個執行緒/