协程实现同步的方式

119 阅读3分钟

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(协程信号量限制)