注册

kotlin的协程异步,并发(同步)

一:协程的异步

任务

private fun task(){
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, start")
Thread.sleep(1000)
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, end")
}

下面使用协程异步的方式,让任务task()在子线程中处理。

方式1:launch()+Dispatchers.IO

launch创建协程;

Dispatchers.IO调度,在子线程处理网络耗时

fun testNotSync() {
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法start")
// 重复执行3次,模拟点击3次
repeat(3) {
CoroutineScope(Dispatchers.IO).launch {
task()
}
}
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法end")

// 防止main函数执行结束,就不管其他线程的打印工作了,哈哈
Thread.sleep(10000)
}

结果:

currentThread:main, time:1631949431058, 方法start
currentThread:main, time:1631949431166, 方法end

currentThread:DefaultDispatcher-worker-3 @coroutine#3, time:1631949431176, start
currentThread:DefaultDispatcher-worker-1 @coroutine#2, time:1631949431182, start
currentThread:DefaultDispatcher-worker-2 @coroutine#1, time:1631949431182, start

currentThread:DefaultDispatcher-worker-3 @coroutine#3, time:1631949432176, end
currentThread:DefaultDispatcher-worker-1 @coroutine#2, time:1631949432182, end
currentThread:DefaultDispatcher-worker-2 @coroutine#1, time:1631949432183, end

显示:主线程内容先执行,然后会在3个子线程异步的执行

方式2:async()+Dispatchers.IO

fun testByCoroutineAsync() {
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法start")
repeat(3){
CoroutineScope(Dispatchers.IO).async {
task()
}
}
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法end")
// 防止main函数执行结束,就不管其他线程的打印工作了,哈哈
Thread.sleep(10000)
}

结果:

currentThread:main @coroutine#1, time:1631957324981, 方法start
currentThread:main @coroutine#1, time:1631957325007, 方法end
currentThread:DefaultDispatcher-worker-1 @coroutine#3, time:1631957325007, start
currentThread:DefaultDispatcher-worker-2 @coroutine#2, time:1631957325007, start
currentThread:DefaultDispatcher-worker-4 @coroutine#4, time:1631957325007, start
currentThread:DefaultDispatcher-worker-1 @coroutine#3, time:1631957326007, end
currentThread:DefaultDispatcher-worker-4 @coroutine#4, time:1631957326007, end
currentThread:DefaultDispatcher-worker-2 @coroutine#2, time:1631957326007, end

显示:主线程内容先执行,然后会在3个子线程异步的执行

看源码发现CoroutineScope.async 等同 CoroutineScope.launch,不同是返回值。

方式3:withContext+Dispatchers.IO

/**
* 单个withContext的异步任务
*/
fun testByWithContext() = runBlocking {
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法start")

withContext(Dispatchers.IO) {
task()
}

println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法end")

// 防止main函数执行结束,就不管其他线程的打印工作了,哈哈
Thread.sleep(10 *1000)
}

结果:

currentThread:main @coroutine#1, time:1631958195591, 方法start
currentThread:DefaultDispatcher-worker-1 @coroutine#1, time:1631958195669, start
currentThread:DefaultDispatcher-worker-1 @coroutine#1, time:1631958196669, end
currentThread:main @coroutine#1, time:1631958196671, 方法end

发现:withContext的task是在子线程中执行,但是也阻塞了main线程,最后执行了"方法end"

因为withContext切io线程后,还挂起了外部的协程(可以理解线程),需要等withCotext执行完成,才会回到原来的协程,也直接可以理解为阻塞了当前的线程。

上面是单个withCotext的异步执行,看多个withContext是怎么样的

fun testByWithContext() = runBlocking {
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法start")
// 重复3次,模拟点击3次
repeat(3) {
println("repeat it = $it")
withContext(Dispatchers.IO) {
task()
}
}

println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法end")

// 防止main函数执行结束,就不管其他线程的打印工作了,哈哈
Thread.sleep(10 *1000)
}

结果:

currentThread:main @coroutine#1, time:1631958027834, 方法start
repeat it = 0
currentThread:DefaultDispatcher-worker-1 @coroutine#1, time:1631958027870, start
currentThread:DefaultDispatcher-worker-1 @coroutine#1, time:1631958028870, end
repeat it = 1
currentThread:DefaultDispatcher-worker-1 @coroutine#1, time:1631958028873, start
currentThread:DefaultDispatcher-worker-1 @coroutine#1, time:1631958029873, end
repeat it = 2
currentThread:DefaultDispatcher-worker-1 @coroutine#1, time:1631958029874, start
currentThread:DefaultDispatcher-worker-1 @coroutine#1, time:1631958030874, end
currentThread:main @coroutine#1, time:1631958030874, 方法end

发现:先main线程执行,然后一个withcontext异步执行完成,才能执行下一个withcontext的异步

实现了多个异步任务的同步,当我们有多个接口请求,需要按顺序执行时,可以使用

二:协程的并发(同步)

Java中并发concurrent的处理,基本使用同步synchronized,Lock,join等来处理。下面我们看看协程怎麽处理的。

1:@Synchronized 注解

我们将上面的任务task修改一下,方法上面加个注解@Synchronized,然后执行launch的异步看能不能同步任务?

使用

/**
* @Synchronized 修改普通函数ok,可以同步
*/
@Synchronized
private fun taskSynchronize(){
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, start")
Thread.sleep(1000)
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, end")
}

测试:aunch异步同时访问taskSynchronize()任务

fun testCoroutineWithSync() {
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法start")
repeat(3){
CoroutineScope(Dispatchers.IO).launch {
taskSynchronize()
}
}
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法end")

// 防止main函数执行结束,就不管其他线程的打印工作了,哈哈
Thread.sleep(10000)
}

结果:

currentThread:main, time:1631959341585, 方法start
currentThread:main, time:1631959341657, 方法end
currentThread:DefaultDispatcher-worker-4 @coroutine#3, time:1631959341657, start
currentThread:DefaultDispatcher-worker-4 @coroutine#3, time:1631959342658, end
currentThread:DefaultDispatcher-worker-1 @coroutine#1, time:1631959342658, start
currentThread:DefaultDispatcher-worker-1 @coroutine#1, time:1631959343658, end
currentThread:DefaultDispatcher-worker-2 @coroutine#2, time:1631959343658, start
currentThread:DefaultDispatcher-worker-2 @coroutine#2, time:1631959344658, end

发现:先main先执行完成,然后每个线程任务,同步执行完成了

问题

当@Synchronized 注解的方法中,有挂起函数且是阻塞的,就不行了

修改一下任务,其中的Thread.sleep(1000)改为delay(1000),看看如何?

/**
* 和方法taskSynchronize(), 不同的是内部使用了delay的挂起函数,而其它会阻塞,需要等它完成后面的才能开始
*
* @Synchronized 关键字不要修饰方法中有suspend挂起函数,因为内部又挂起了,就不会同步了
*/
@Synchronized
suspend fun taskSynchronizeByDelay(){
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, start")
delay(1000)
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, end")
}
/**
* 执行体是taskSynchronizeByDelay(), 内部会使用delay函数,导致他外部的线程挂起,其他线程可以访问执行体,
*
* 所以:@Synchronized 同步注解,尽量不用修饰suspend的函数
*/
fun testCoroutineWithSync2() {
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法start")
repeat(3){
CoroutineScope(Dispatchers.IO).launch {
taskSynchronizeByDelay()
}
}
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法end")

// 防止main函数执行结束,就不管其他线程的打印工作了,哈哈
Thread.sleep(10000)
}

结果:

currentThread:main, time:1631961179390, 方法start
currentThread:main, time:1631961179451, 方法end
currentThread:DefaultDispatcher-worker-1 @coroutine#1, time:1631961179456, start
currentThread:DefaultDispatcher-worker-4 @coroutine#3, time:1631961179464, start
currentThread:DefaultDispatcher-worker-3 @coroutine#2, time:1631961179464, start
currentThread:DefaultDispatcher-worker-3 @coroutine#1, time:1631961180462, end
currentThread:DefaultDispatcher-worker-3 @coroutine#3, time:1631961180464, end
currentThread:DefaultDispatcher-worker-4 @coroutine#2, time:1631961180464, end

发现:加了@Synchronized注解,还是异步的执行,因为task中有delay这个挂起函数,它会挂起外部协程,直到执行完成才会执行其他的。

2:Mutex()

使用:

var mutex = Mutex()
mutex.withLock {
// TODO
}

测试:

fun testSyncByMutex() = runBlocking {
var mutex = Mutex()
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法start")
repeat(3){
CoroutineScope(Dispatchers.IO).launch {
mutex.withLock {
task()
}
}
}
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法end")

// 防止main函数执行结束,就不管其他线程的打印工作了,哈哈
Thread.sleep(10000)
}

结果:

currentThread:main @coroutine#1, time:1631951230155, 方法start
currentThread:main @coroutine#1, time:1631951230178, 方法end
currentThread:DefaultDispatcher-worker-1 @coroutine#2, time:1631951230178, start
currentThread:DefaultDispatcher-worker-1 @coroutine#2, time:1631951231178, end
currentThread:DefaultDispatcher-worker-2 @coroutine#3, time:1631951231183, start
currentThread:DefaultDispatcher-worker-2 @coroutine#3, time:1631951232183, end
currentThread:DefaultDispatcher-worker-1 @coroutine#4, time:1631951232183, start
currentThread:DefaultDispatcher-worker-1 @coroutine#4, time:1631951233184, end

发现:多个异步任务同步完成了。

3:Job.join()

Job创建协程返回的句柄,它支持join()操作,类是java线程的join功能,可以等待任务执行完成,实现同步

测试:

fun testSyncByJob() = runBlocking{
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法start")
repeat(3){
var job = CoroutineScope(Dispatchers.IO).launch {
task()
}
job.start()
job.join()
}
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法end")

// 防止main函数执行结束,就不管其他线程的打印工作了,哈哈
Thread.sleep(10000)
}

结果:

currentThread:main @coroutine#1, time:1631959997427, 方法start
currentThread:DefaultDispatcher-worker-1 @coroutine#2, time:1631959997507, start
currentThread:DefaultDispatcher-worker-1 @coroutine#2, time:1631959998507, end
currentThread:DefaultDispatcher-worker-1 @coroutine#3, time:1631959998509, start
currentThread:DefaultDispatcher-worker-1 @coroutine#3, time:1631959999509, end
currentThread:DefaultDispatcher-worker-1 @coroutine#4, time:1631959999510, start
currentThread:DefaultDispatcher-worker-1 @coroutine#4, time:1631960000510, end
currentThread:main @coroutine#1, time:1631960000510, 方法end

发现:多个任务可以同步一个个完成,并且阻塞了main线程,和withContext的效果一样哦。

4:ReentrantLock

使用:

val lock = ReentrantLock()
lock.lock()
task()
lock.unlock()

测试:

fun testReentrantLock2() = runBlocking {

val lock = ReentrantLock()
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法start")
repeat(3){
CoroutineScope(Dispatchers.IO).launch {
lock.lock()
task()
lock.unlock()
}
}
println("currentThread:${Thread.currentThread().name}, time:${System.currentTimeMillis()}, 方法end")

// 防止main函数执行结束,就不管其他线程的打印工作了,哈哈
Thread.sleep(10000)
}

结果:

currentThread:main @coroutine#1, time:1631960884403, 方法start
currentThread:main @coroutine#1, time:1631960884445, 方法end
currentThread:DefaultDispatcher-worker-1 @coroutine#2, time:1631960884445, start
currentThread:DefaultDispatcher-worker-1 @coroutine#2, time:1631960885446, end
currentThread:DefaultDispatcher-worker-5 @coroutine#4, time:1631960885446, start
currentThread:DefaultDispatcher-worker-5 @coroutine#4, time:1631960886446, end
currentThread:DefaultDispatcher-worker-2 @coroutine#3, time:1631960886446, start
currentThread:DefaultDispatcher-worker-2 @coroutine#3, time:1631960887447, end

发现:同步完成。

0 个评论

要回复文章请先登录注册