(1)目的本质上存在差异
线程目的是提高CPU资源使用率,使多个任务得以并行的运行,是为了服务于机器的
协程目的是让多个任务之间更好的协作,主要体现在代码逻辑上,是为了服务于人的
(2)调度上
线程的调度是系统完成的, 一般是抢占式的,根据优先级来分配,是空分复用
协程的调度是开发者指定好的,在不同时期把资源合理地分配给不同任务,是时分复用的
(3)两者的关系
协程并不是取代线程,而且抽象于线程之上,线程是被分割的CPU资源,协程是组织好的代码流程,协程需要线程来承载运行,线程是协程的资源,但协程不会直接使用线程,协程直接利用的是执行器(Interceptor), 执行器可以关联任意线程或线程池
导入依赖
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
使用GlobalScope开启协程
GlobalScope.launch {
Log.d(TAG, Thread.currentThread().name)
}
我们可以通过log看到,它执行在子线程
当然我们也可以指定线程,比如让它运行在主线程
GlobalScope.launch (Dispatchers.Main){
Log.d(TAG, Thread.currentThread().name)
}
由此可见,协程可以让我们开启一个线程,是一个线程框架
好了,看到这,相信大家已经知道协程是什么东东了,但是,在协程中,还有一个非常好用的函数是withContext,这个函数可以切换到指定线程,并在闭包中的逻辑执行完后自动把线程切回去继续执行
CoroutineScope(Dispatchers.Main).launch {
println(Thread.currentThread().name)
withContext(Dispatchers.IO) { //切换到 IO 线程
Log.d("jayio",Thread.currentThread().name)
}
Log.d("jaymain",Thread.currentThread().name) //主线程更新
}
由于可以自动切回来,我们甚至可以把 withContext 放进一个单独的函数里面
suspend fun getCoroutine(): Boolean = withContext(Dispatchers.IO) {
//...执行操作
}
例如,我们要在网络上下载图片然后显示出来,可以这样做
private suspend fun getImage(): Bitmap = withContext(Dispatchers.IO) {
OkHttpClient().newCall(
Request.Builder()
.url(url)
.get()
.build()
)
.execute().body?.byteStream().use {
BitmapFactory.decodeStream(it)
}
}
CoroutineScope(Dispatchers.Main).launch {
img.setImageBitmap(getImage())
}
协程指的就是 launch 中的代码,其实挂起的对象就是协程。当执行 launch 时,在执行到某一个 suspend 函数时,这个协程就会被挂起。让时间静止,兵分两路,来看一下到底是怎么回事,这两路分别是协程和线程(UI线程)。如果你某个函数比较耗时,也就是需要等,就可以把它写成 suspend 函数。再看上面的例子,当主线程执行getImage 时,会跳出协程。总而言之,协程在执行到有 suspend 的时候就会被挂起,而这个挂起,则就是切个线程;只不过挂起的执行完后会重新切回他原来的线程,这个切回来的动作,在 Kotlin 中叫做 resume(恢复)
当然并不是只有 withContext 来辅助我们实现自定义的函数,如 delay ,他的作用是等一段时间后在继续往下执行代码
suspend fun waitTime() {
delay(5) //挂起
}
suspend 的意义
其实 suspend 关键字没有实际的挂起,因为他本来就不是用来操作挂起的。也就是说切线程依赖的是函数中的代码,而不是这个关键字,这个关键字只是用来提醒,只有一个效果,那就是限制此函数只能在协程中被调用,如果在非协程中调用,则编译不会通过
当然,我们也可以将回调写成挂起函数
private suspend fun getImagenew() = suspendCoroutine<Bitmap> { continuation ->
OkHttpClient().newCall(
Request.Builder()
.url(url)
.get()
.build()
)
.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
continuation.resumeWithException(e)
}
override fun onResponse(call: Call, response: Response) {
continuation.resume(
response.body?.byteStream().use { BitmapFactory.decodeStream(it) })
}
})
}
非阻塞式挂起并没有限定在一个线程中,因为挂起本来就涉及到多个线程。主线程执行的时候遇到耗时任务,然后将耗时任务挂起,这时主线程就自由了,可以继续做别的事了。所以非阻塞式挂起其实就是在讲 协程在挂起的时候切换线程这件事
delay
fun sayHellow(){
GlobalScope.launch {
delay(1000L) //协程挂起,阻塞1秒
Log.d("jaydelay"," world")
}
Log.d("jay","hellow") //协程挂起时,主线程继续执行
Thread.sleep(2000L)//延时,保证主线程存活
}
fun sayHellow() {
runBlocking {
//此处还是主线程
GlobalScope.launch {
delay(1000L)
}
delay(2000L) //延时,保证主线程存活,其实在runBlocking里面不需要这句也行
}
}
suspend fun sayHellow() {
val job = GlobalScope.launch {
delay(1000L)
}
job.join() //等待子线程执行结束
}
该方法被 suspend 修饰了,因为 join 方法被 suspend 修饰过,suspend 本身不会挂起,挂起是因为join 内部有挂起的代码,suspend 只是一个提示,只是这个提示必须写
fun sayHellow() = runBlocking {
//开始执行主协程
launch {
delay(1000L)
Log.d("ha1", " world")
}//在最后并没有让主线程等待,也没有调用 join,依然会打印出 world
Log.d("ha2", "Hellow")
}
在 runBlocking 内的每个协程构建器中都将 CoruntineScope 的实例添加到代码块的作用域中。我们可以在这个作用域中启动协程而无需显示调用 join。因为外部协程(runBlocking)直到在其作用域中启动的所有协程执行完毕后才会结束