www.studyyoun.com/study_andro…html
多線程操做數據庫,爲處理併發問題,你們第一想到的是加鎖操做 ,SQLite是文件級別的鎖.SQLite3對於併發的處理機制是容許同一個進程的多個線程同時讀取一個數據庫,可是任什麼時候刻只容許一個線程/進程寫入數據庫。在操行寫操做時,數據庫文件被瑣定,此時任何其餘讀/寫操做都被阻塞,若是阻塞超過5秒鐘(默認是5秒,能太重新編譯sqlite能夠修改超時時間),就報"database is locked"錯誤java
SQLiteDatabaseLockedException: database is locked和java.lang.IllegalStateException: attempt to re-open an already-closed object.這兩個是咱們最多見的數據庫併發異常android
SQLiteDatabaseLockedException: database is locked 異常可經過 Android 數據庫綜述(一) 中的方法,也就是將SqliteHelper對象設置爲單例就能夠解決sql
// 私有的構造函數,只能本身使用,防止繞過同步方法生成多個實例,
private SqlDBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
/私有的靜態對象,爲整個應用程序提供一個sqlite操做的靜態實例,
//並保證只能經過下面的靜態方法getHelper(Context context)得到,
//防止使用時繞過同步方法改變它
//這裏主要解決死鎖問題,是static就能解決死鎖問題
private static SqlDBHelper instance;
/**
* 爲應用程序提供一個單一的入口,保證應用程序使用同一個對象操做數據庫,不會由於對象不一樣而使同步方法失效
* 其實就是獲取數據庫操做的實例
* @param context 上下文
* @return instance
*/
public static SqlDBHelper getHelper(Context context) {
if (instance == null)
instance = new SqlDBHelper(context);
return instance;
}複製代碼
線程A打開數據,正在使用數據庫,這時cpu片斷分到線程B,線程A掛起。線程B進入執行獲取打開db時沒有問題,線程B進行操做,在片斷時間內數據操做完成,最後關閉數據庫database.close()。線程B執行結束,線程A執行,插入數據或者其餘操做。。。我靠,怎麼數據庫關閉了呢,而後拋出java.lang.IllegalStateException: attempt to re-open an already-closed object異常數據庫
加同步鎖是其中的一種解決方案,我這裏提供一種更優雅的方式來處理 併發問題,就是使用計數器原理 ,結合 CountDownLatch 與 Semaphore這兩個類來完成bash
CountDownLatch是在java1.5被引入的,跟它一塊兒被引入的併發工具類還有CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue,它們都存在於java.util.concurrent包下。CountDownLatch這個類可以使一個線程等待其餘線程完成各自的工做後再執行。例如,應用程序的主線程但願在負責啓動框架服務的線程已經啓動全部的框架服務以後再執行多線程
CountDownLatch是經過一個計數器來實現的,計數器的初始值爲線程的數量。每當一個線程完成了本身的任務後,計數器的值就會減1。當計數器值到達0時,它表示全部的線程已經完成了任務,而後在閉鎖上等待的線程就能夠恢復執行任務。併發
Semaphore也是一個線程同步的輔助類,能夠維護當前訪問自身的線程個數,並提供了同步機制。使用Semaphore能夠控制同時訪問資源的線程個數,例如,實現一個文件容許的併發訪問數。
Semaphore是一種基於計數的信號量。它能夠設定一個閾值,基於此,多個線程競爭獲取許可信號,作完本身的申請後歸還,超過閾值後,線程申請許可信號將會被阻塞。Semaphore能夠用來構建一些對象池,資源池之類的,好比數據庫鏈接池,咱們也能夠建立計數爲1的Semaphore,將其做爲一種相似互斥鎖的機制,這也叫二元信號量,表示兩種互斥狀態框架
// 建立一個計數閾值爲5的信號量對象
// 只能5個線程同時訪問
Semaphore semp = new Semaphore(5);
try {
// 申請許可
semp.acquire();
try {
// 業務邏輯
} catch (Exception e) {
} finally {
// 釋放許可
semp.release();
}
} catch (InterruptedException e) {
}複製代碼
public class ArtSqlDBHelper {
//數據庫實例對象
private SQLiteDatabase mLiteDatabase;
//數據庫輔助類操做對象
protected static final SqlDBHelper mSqlDBHelper;
/**
* CountDownLatch是JAVA提供在java.util.concurrent包下的一個輔助類,
* 能夠把它當作是一個計數器,其內部維護着一個count計數,只不過對這個計數器的操做都是原子操做,同時只能有一個線程去操做這個計數器,
* CountDownLatch經過構造函數傳入一個初始計數值,調用者能夠經過調用CounDownLatch對象的cutDown()方法,來使計數減1;
* 若是調用對象上的await()方法,那麼調用者就會一直阻塞在這裏,直到別人經過cutDown方法,將計數減到0,才能夠繼續執行
*/
/**
* 在這裏建立了一個計數器,初始值爲0 ,也就是說當前有0個線程在操做
*/
private static CountDownLatch latch =
new CountDownLatch(0);
/**
* 建立一個信號量,初始值爲1 只容許一個線程來操做
* 經過初始值爲1的Semaphore,很好的實現了資源的互斥訪問
*/
private static Semaphore lock =
new Semaphore(1, true);
public ArtSqlDBHelper(Context context) {
super(context);
//經過內部方法獲取靜態對象
mSqlDBHelper =SqlDBHelper.getHelper(context);
}
}複製代碼
這裏是執行的是批量操做,與單條數據的操做思想一至函數
public void insertArtList(List<ArtModel> artModelList) {
try {
if (latch.getCount() == 0 || mLiteDatabase == null) {
mLiteDatabase = mSqlDBHelper.getWritableDatabase();
}
//await()方法,那麼調用者就會一直阻塞在這裏,直到別人經過cutDown方法,將計數減到0
latch.await();
//請求許可
lock.acquire();
// 開啓事務
mLiteDatabase.beginTransaction();
// 循環插入數據
for (ArtModel artModel : artModelList) {
//構建 ContentValues
ContentValues classValues = new ContentValues();
classValues.put("art_id", artModel.getId());
classValues.put("user_id", articlPariseModel);
//執行操做
mLiteDatabase.insert("t_art_list", null, classValues);
}
// 操做成功
mLiteDatabase.setTransactionSuccessful();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//結束事務
mLiteDatabase.endTransaction();
//釋放許可
lock.release();
if (latch.getCount() == 1) {
//關閉數據庫
mLiteDatabase.close();
mLiteDatabase = null;
}
//計數器減1
latch.countDown();
}
}複製代碼
/**
* 刪除
* 清除數據庫中的數據
*/
public void clearArtDb() {
try {
if (latch.getCount() == 0 || mLiteDatabase == null) {
mLiteDatabase = mSqlDBHelper.getWritableDatabase();
}
latch.await();
lock.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
//這裏沒指定 where 限定條件,刪除表中的的全部的數據
String clearsQL = "delete from t_art_list ";
//執行
mLiteDatabase.execSQL(clearsQL);
lock.release();
if (latch.getCount() == 1) {
mLiteDatabase.close();
mLiteDatabase = null;
}
latch.countDown();
}複製代碼
public void updateArtList(List<ArtModel> artModelList) {
try {
if (latch.getCount() == 0 || mLiteDatabase == null) {
mLiteDatabase = mSqlDBHelper.getWritableDatabase();
}
latch.await();
lock.acquire();
// 開啓事務
mLiteDatabase.beginTransaction();
// 循環更新數據
for (ArtModel artModel : artModelList) {
//構建 ContentValues
ContentValues classValues = new ContentValues();
classValues.put("art_id", artModel.getId());
classValues.put("user_id", articlPariseModel);
//執行操做
mLiteDatabase.update("t_art_list", classValues, "art_id=?", new String[]{String.valueOf(artModel.getId())});
}
// 操做成功
mLiteDatabase.setTransactionSuccessful();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mLiteDatabase.endTransaction();
lock.release();
if (latch.getCount() == 1) {
mLiteDatabase.close();
mLiteDatabase = null;
}
latch.countDown();
}
}複製代碼
public List<ArtModel> queryArtModelList(int type) {
try {
if (latch.getCount() == 0 || mLiteDatabase == null) {
mLiteDatabase = mSqlDBHelper.getReadableDatabase();
}
latch.await();
lock.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
List<ArtModel> list = new ArrayList<>();
String sql = "select * from " + SqlDBHelper.TABLE_NAME_ART;
if (type == 0) {
sql = sql + " where art_is_top=1 or art_is_recommend=1";
}
Cursor cursor = mLiteDatabase.rawQuery(sql, null);
if (cursor != null) {
while (cursor.moveToNext()) {
ArtModel artModel = new ArtModel();
artModel.id = cursor.getInt(cursor.getColumnIndex("art_id"));
artModel.artName = cursor.getString(cursor.getColumnIndex("art_name"));
... ...
list.add(artModel);
}
}
lock.release();
if (latch.getCount() == 1) {
mLiteDatabase.close();
mLiteDatabase = null;
}
latch.countDown();
return list;
}複製代碼