目前找到三种方式
- 切换到单线程获取单例
- 使用Coroutine提供的Mutex获取单例
- 使用CAS(AtomicReference)获取单例
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.util.concurrent.atomic.AtomicReference
/** 使用协程,如何获取单例对象 */
class SingleInstance private constructor(val token: String) {
companion object {
@Volatile
private var instance: SingleInstance? = null
/** 切换到同一个线程实现 */
suspend fun getInstance(): SingleInstance {
return withContext(Dispatchers.Main.immediate) {
instance ?: createInstance().also {
instance = it
}
}
}
/** Mutex不是可重入的,要注意死锁的问题 */
private val mutex = Mutex()
/** 使用Coroutine提供的Mutex实现 */
suspend fun getInstance2(): SingleInstance {
return mutex.withLock {
instance ?: createInstance().also {
instance = it
}
}
}
private var instanceRef = AtomicReference<SingleInstance>()
/** 使用CAS实现*/
suspend fun getInstance3(): SingleInstance {
while (true) {
val instance = instanceRef.get()
if (instance != null) {
return instance
}
// createInstance可能会调用多次,但是只会设置成功一次
instanceRef.compareAndSet(null, createInstance())
}
}
private suspend fun createInstance(): SingleInstance {
val token = getTokenFromRemote()
return SingleInstance(token)
}
}
}
/** 模拟网络请求获取token */
private suspend fun getTokenFromRemote(): String {
return withContext(Dispatchers.IO) {
delay(500)
"This is the token request by retrofit"
}
}
三种方式的对比:
- 切换到单线程的方式
- 优点:逻辑简单
- 缺点:切换线程的开销和延迟,不适用没有固定单线程的系统(比如非GUI的Java项目中没有MainThread)
- 使用场景: 不追求极致性能的场景
- 使用Mutex的方式
- 优点:没有切换线程的开销和延迟
- 缺点:锁不能可重入的,如果情况比较复杂很容易发生死锁
- 使用场景: 使用锁比较少的场景
- CAS的方式
- 优点:无锁,没有切换线程的开销和延迟
- 缺点:对象实例可能会创建多个
- 使用场景: 适合创建对象资源消耗比较小,只需要使用单例来保证系统的状态唯一性的情况
另外注意:
- 不能使用sychronized 和Java的ReentrantLock来实现,因为java的锁都是锁线程,而协程在同一个函数里,可能会发生线程切换,所以无法使用java的线程锁
网友评论