Android 数据库综述(二) 程序计算器与信号量来处理多线程并发问题


多线程操作数据库,为处理并发问题,大家第一想到的是加锁操作 ,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;
}