问题描述
实际使用中在同一时间,两个线程分别进行同一个表(litepal中一个表,对应着一个类)的查询与实例化操作,存在着死锁的问题。
下面来自 LitePal GitHub issue中一位网友的描述:
实例化User类的时候发生的事情,先初始化User类的过程中持有User.class锁,发现DataSupport是父类,此时User.class处于being_initialized状态,尝试初始化DataSupport类(想进入being_initialized状态),但是DataSupport.class已经被子线程的查询操作访问先进行初始化(已经进入了being_initialized状态,但查询线程需要等待User的being_initialized状态完成),所以主线程实例化User的时候持有User.class锁,等待DataSupport.class锁;而子线程的查询User操作的时候,先访问的DataSupport类,先初始化DataSupport类,等待访问User.class锁,顺序相反地持锁,死锁BOOM!如果您坚持认为这个是程序逻辑死锁的话,那我们使用LitePal时就不能查询的时候同时在另一个线程实例化该类了。算是库的缺陷么?
类初始化过程持锁的原因 参考链接:https://yq.aliyun.com/articles/73595
代码 & 解决
可能出现死锁的伪代码如下:
class User():LitePalSupport()
// A
Thread(){
...
Log.i("before LitePal#findAll")
LitePal.findAll<User>()
Log.i("after LitePal#findAll")
}.start()
// B
Thread(){
...
Log.i("before User instantiation")
val user = User()
Log.i("after User instantiation")
}.start()
如上代码, 当线程A执行到 LitePal.findAll<User>()
持有 LitePalSupport.class锁 并等待 User.class的释放 同时 线程B执行到 val user = User()
持有 User.class锁 并等待 LitePalSupport.class 的释放。
由于 User 继承自 LitePalSupport ,所以在User 类初始化时,需要等待 LitePalSupport.class 锁
解决:
class User():LitePalSupport()
val lock = Any()
// A
Thread(){
...
Log.i("before LitePal#findAll")
Synchronized(lock){ LitePal.findAll<User>() }
Log.i("after LitePal#findAll")
}.start()
// B
Thread(){
...
Log.i("before User instantiation")
val user = Synchronized(lock){ User() }
Log.i("after User instantiation")
}.start()
思路:
- 实例化与查询保持在用一个线程中进行
- 避免死锁,自己手动增加一个锁。