1. 协程出现同步问题
实现一个并发,由于数据量很大,这些计算我们在子线程实现,不使用同步会存在同步问题
/** 使用非同步方式 47ms */
@Test
fun testSimple() {
runBlocking {
val start = System.currentTimeMillis()
var count = 0
List(1000) {
CoroutineScope(Dispatchers.Default).launch {
repeat(10000) {
count++
}
}
}.joinAll()
println("count值为:${count},非同步耗时为:${System.currentTimeMillis() - start}")
}
}
执行结果:count值为:7229509(不为10000000),非同步耗时为:47
2. 同步方案
2.1 synchronized 关键字
/** 使用synchronized线程锁实现同步 130ms */
@Test
fun testSynchronized() {
runBlocking {
val start = System.currentTimeMillis()
var count = 0
val name = "123"
List(1000) {
CoroutineScope(Dispatchers.Default).launch {
repeat(10000) {
synchronized(name) {
count++
}
}
}
}.joinAll()
println("count值为:${count},Synchronized耗时为:${System.currentTimeMillis() - start}")
}
}
执行结果:count值为:10000000,Synchronized耗时为:130
2.2 ReentrantLock可重入锁
/** 使用ReentrantLock线程锁实现同步 241ms*/
@Test
fun testReentrantLock() {
runBlocking {
val start = System.currentTimeMillis()
var count = 0
val lock = ReentrantLock()
List(1000) {
CoroutineScope(Dispatchers.Default).launch(Dispatchers.Default) {
repeat(10000) {
lock.lock()
count++
lock.unlock()
}
}
}.joinAll()
println("count值为:${count},ReentrantLock耗时为:${System.currentTimeMillis() - start}")
}
}
执行结果:count值为:10000000,ReentrantLock耗时为:241
2.3 AtomicInteger原子操作
除了线程锁和协程锁的方法,我们还能使用 AtomicInteger 包装count,实现原子操作,从而间接的实现锁的效果。
/** 使用Atomic原子操作实现同步 140ms */
@Test
fun testAtomic() {
runBlocking {
val start = System.currentTimeMillis()
val atomicCount = AtomicInteger(0)
List(1000) {
CoroutineScope(Dispatchers.Default).launch(Dispatchers.Default) {
repeat(10000) {
atomicCount.incrementAndGet()
}
}
}.joinAll()
println("count值为:${atomicCount.get()},Atomic原子操作耗时为:${System.currentTimeMillis() - start}")
}
}
执行结果:count值为:10000000,Atomic原子操作耗时为:140
2.4 mutex的实现
之前的方式都是我们线程同步的概念,接下来我们可以看看协程专用同步工具 mutex 如何使用:
/** 使用Mutex协程锁实现同步 14881ms */
@Test
fun testMutex() {
runBlocking {
val start = System.currentTimeMillis()
var count = 0
val mutex = Mutex()
List(1000) {
CoroutineScope(Dispatchers.Default).launch {
repeat(10000) {
mutex.withLock {
count++
}
}
}
}.joinAll()
println("count值为:${count},Mutex耗时为:${System.currentTimeMillis() - start}")
}
}
执行结果:count值为:100000000,Mutex耗时为:14881
2.5 Semaphore 指定通道数量
Semaphore是协程中的信号量 ,我们指定通行的数量为1,那么就可以保证并发的数量为1,这样异曲同工达到锁的效果。
/** 使用Semaphore协程信号量实现同步 1518ms */
@Test
fun testSemaphore() {
runBlocking {
val start = System.currentTimeMillis()
var count = 0
val semaphore = Semaphore(1)
List(1000) {
CoroutineScope(Dispatchers.Default).launch {
repeat(10000) {
semaphore.withPermit {
count++
}
}
}
}.joinAll()
println("count值为:${count},Semaphore耗时为:${System.currentTimeMillis() - start}")
}
}
执行结果:count值为:10000000,Semaphore耗时为:1518
2.6 actor并发同步模型
actor 是创建协程的一种,是特殊的协程,继承Channel
可以简单的理解为并发的2个协程与actor的协程通信,actor是一个特殊的协程,保证了单一的原则。从而间接达到锁的效果
/** 使用actor并发同步模型实现同步 4497ms */
@OptIn(ObsoleteCoroutinesApi::class)
@Test
fun testActor() {
runBlocking {
val start = System.currentTimeMillis()
var count = 0
val actor = actor<Int>(capacity = Channel.UNLIMITED) {
for (msg in channel) {
count++
}
}
List(1000) {
CoroutineScope(Dispatchers.Default).launch {
repeat(10000) {
actor.send(1)
}
}
}.joinAll()
actor.close()
println("count值为:${count},Actor耗时为:${System.currentTimeMillis() - start}")
}
}
执行结果:count值为:10000000,Actor耗时为:4497
3. 执行速度对比
优化完成之后我们可以看到并发锁的效率 :synchronized(线程同步锁)≈ AtomicInteger(原子操作)> ReentrantLock(可重入锁) > actor通信并发同步模型 > Mutex(协程互斥锁)≈ Semaphore(协程信号量限制)