对本地文件进行操作的时候我们需要注意多线程安全,最安全和最高效的方法是在同一时刻支持多读单写,也就是同时可以并发读取操作,但是写操作是同步的,在多线程中有个名词叫做栅栏,iOS 和 Android 都有相应的 API,我们可以把栅栏看成是一个串行的队列,所以栅栏是同步一个紧挨着一个执行的,这样就可以确保我们对数据库的操作是串行的,那么就不会出现数据混乱,同时在每一个栅栏里面的区间的操作是并发的,举个例子:
在时间线上我们需要对数据库进行以下操作 写写读读写读写
如果我们用栅栏的方式来做,那么就是创建一个队列,然后划分成几个栅栏,这里是写|写|读读|写|读|写。我们可以看到其中有一个区间是|读读|,因为读操作是不会导致数据错乱的,所以在时间线上,只要先执行了写|写|操作后,并发读操作和同步读操作得到的结果是一样的,为了提高效率我们在这个时候可以并发操作,这就是栅栏。
在 iOS上的 API 如下:
void dispatch_barrier_sync(dispatch_queue_t queue,
DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_barrier_async(dispatch_queue_t queue,
DISPATCH_NOESCAPE dispatch_block_t block);
在 Java 中是使用CyclicBarrier.java
(我还没用过,看了一下应该是它了,如果有误请指正谢谢)
栅栏的操作适用于对性能和安全有极高要求的情况,否则一般情况下使用栅栏会挺麻烦的,因为区间中有多少操作这个是不确定的,所以需要我们在客户端手动设置栅栏,然后再去调用服务端的查询接口。
如果我们对性能的要求不是极高,我们可以退而求其次,只用一个线程来进行数据的增删查改,因为对数据库的操作是一个 IO 操作,如果数据量大则会明显的阻塞主线程,因此我们需要开启一条子线程来执行这些操作,为了避免数据出现错乱,我们则需要一个 FIFO 的队列来缓存这些操作,然后每次都从队列中出队一个操作,将其放在子线程中进行操作。
这样我们我们可以确保操作的实现是顺序的,而且不会阻塞主线程。
下面说一下具体的实现方式,
在 iOS 上,我们使用 GCD,关键是创建一个串行队列,代码如下
@interface RNDatabaseClient()
@property(nonatomic, strong)FMDatabase *db;
@property(nonatomic, strong)dispatch_queue_t queue; //必须是串行队列
@end
@implementation RNDatabaseClient
- (void)deleteWithSql:(NSString *)sql Callback:(id<Callback>)callback {
dispatch_async(self.queue, ^{
BOOL result = [self deleteWithSql:sql];
dispatch_sync(dispatch_get_main_queue(), ^{
if (callback != nil && [callback respondsToSelector:@selector(onResult:)]) {
[callback onResult:result];
}
});
});
}
在 Android 上,我们使用 RxJava,需要注意的是我们使用的调度器应该是Schedulers.single()
或者是核心线程数为1,最大线程数为1的线程池。
public void query(String table, String whereClause, String[] whereArgs, DataBackQueryCallback callback) {
if (callback == null) return;
Disposable disposable = Observable.create(emitter -> {
List<Map<String, String>> list = query(table, whereClause, whereArgs);
emitter.onNext(list);
}).subscribeOn(Schedulers.single())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((list) -> callback.onResult((List<Map<String, String>>) list));
}
总结
- 在对IO进行操作的时候我们需要在子线程中执行任务。
- 我们并发操作数据的时候需要注意并发编程带来的问题,使用栅栏隔离增删改操作,或者用一个串行队列管理所有操作。