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

趙子龍發表於2019-02-21

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

www.studyyoun.com/study_andro…


多執行緒運算元據庫,為處理併發問題,大家第一想到的是加鎖操作 ,SQLite是檔案級別的鎖.SQLite3對於併發的處理機制是允許同一個程式的多個執行緒同時讀取一個資料庫,但是任何時刻只允許一個執行緒/程式寫入資料庫。在操行寫操作時,資料庫檔案被瑣定,此時任何其他讀/寫操作都被阻塞,如果阻塞超過5秒鐘(預設是5秒,能過重新編譯sqlite可以修改超時時間),就報”database is locked”錯誤

SQLiteDatabaseLockedException: database is locked和java.lang.IllegalStateException: attempt to re-open an already-closed object.這兩個是我們最常見的資料庫併發異常

SQLiteDatabaseLockedException: database is locked 異常可通過 Android 資料庫綜述(一) 中的方法,也就是將SqliteHelper物件設定為單例就可以解決

    // 私有的建構函式,只能自己使用,防止繞過同步方法生成多個例項,
    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這兩個類來完成

CountDownLatch

CountDownLatch是在java1.5被引入的,跟它一起被引入的併發工具類還有CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue,它們都存在於java.util.concurrent包下。CountDownLatch這個類能夠使一個執行緒等待其他執行緒完成各自的工作後再執行。例如,應用程式的主執行緒希望在負責啟動框架服務的執行緒已經啟動所有的框架服務之後再執行

CountDownLatch是通過一個計數器來實現的,計數器的初始值為執行緒的數量。每當一個執行緒完成了自己的任務後,計數器的值就會減1。當計數器值到達0時,它表示所有的執行緒已經完成了任務,然後在閉鎖上等待的執行緒就可以恢復執行任務。

Semaphore

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) {

}複製程式碼
CountDownLatch 與 Semaphore 結合對資料的增冊改查
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;
}複製程式碼

相關文章