我们知道JVM中的线程的实现是依赖其运行的操作系统决定的,JVM只是在上层进行了API的封装,包含常见的有线程的启动方法,状态的管理,比如:Java中抽象出了6种状态,提供了start方法用于启动线程。
但是线程一旦调用start()开始执行,那我们是很难再控制线程的停止的,尽管jdk中提供了suspend()方法,但是suspend也只是做了标记线程需要中断,最终是否中断,什么时候中断还是依赖操作系统的具体实现逻辑,从语言层面来说是无法直接控制的。
// java线程的状态定义在Java$State枚举对象中
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
*
* - {@link Object#wait() Object.wait} with no timeout
* - {@link #join() Thread.join} with no timeout
* - {@link LockSupport#park() LockSupport.park}
*
*
* A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
*
* - {@link #sleep Thread.sleep}
* - {@link Object#wait(long) Object.wait} with timeout
* - {@link #join(long) Thread.join} with timeout
* - {@link LockSupport#parkNanos LockSupport.parkNanos}
* - {@link LockSupport#parkUntil LockSupport.parkUntil}
*
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
而协程内的代码依然执行在线程上,因为线程是CPU调度的基本单元这个大前提还是不变的,属于操作系统层面的基本概念了。但是协程通过使用状态机的方式在语言层面上实现了一种状态、生命周期更易管控的代码逻辑调度框架(语言层面的框架),也可以理解为轻量级线程,并且不像线程那样直接使用操作系统实现的线程,一旦启动基本就只能等任务执行结束或请求中断待能中断时才停止运行。
启动一个线程执行任务:
val task1 = Thread {
val result = requestUserInfo()
println("task1 finished, result = $result")
}
task1.start()
启动一个协程执行任务:
val task1 = launch {
val result = requestUserInfo()
println("task1 finished, result = $result")
}
// requestUserInfo()需要切换协程运行的线程需要增加suspend修饰,
// 定义成挂起函数
suspend fun requestUserInfo(): UserInfo = withContext(Dispatchers.IO) {
delay(500)
return@withContext UserInfo("10000", "zhangsan")
}
总结一下,协程和线程的区别:
创建协程或调用挂起函数必须有协程作用域,kotlin创建作用域有三种办法,GlobalScope、runBlocking和CoroutineScope()方法。
public interface Job : CoroutineContext.Element {
// 注(1)
public companion object Key : CoroutineContext.Key<Job>
// 如果协程还未启动,比如传入的start对象是LAZY,可通过主动调用
// start方法启动协程
public fun start(): Boolean
// 注(2)
public fun cancel(cause: CancellationException? = null)
// 当前协程的子协程
public val children: Sequence<Job>
// 附加子协程,使当前协程对象成为父协程
@InternalCoroutinesApi
public fun attachChild(child: ChildJob): ChildHandle
// 等待当前协程执行完成,比如调用协程的cancel()方法后,调用join()
// 就是等待协程cancel执行完成
public suspend fun join()
// 注册在取消或完成此作业时 同步 调用一次的处理程序
public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
@InternalCoroutinesApi
public fun invokeOnCompletion(
onCancelling: Boolean = false,
invokeImmediately: Boolean = true,
handler: CompletionHandler): DisposableHandle
}
存放协程相关的一些信息
// 场景1: 在子线程中执行耗时任务后,切到主线程处理
coroutineScope.launch {
// 挂起函数,执行时从当前线程中脱离,执行在dispatcher执行的线程中,执行完毕后再切换原来的线程中
// 挂起后当前协程下一行代码会等待挂起函数执行完成
val result = withContext(Dispatchers.IO) {
// 在Dispatchers.IO(线程调度器)指定的子线程中执行下面的代码
delay(5000)
100
}
Log.d(TAG, "onCreate: main 2 =========> $coroutineContext")
binding.tvNews.text = result.toString()
}
上面的用法对于Android来说,协程是一个异步代码执行框架,相比于Thread+Handler的方式更加简洁,省去了开发者编写线程切换代码的工作。
在Android业务中我们经常需要并行开始多个业务接口请求,然后合并成一个结果,进行后续业务逻辑的判断、UI的展示,使用Jdk提供的CountDownLatch,RxJava的zip都可以实现类似的功能逻辑。
如下展示了kotlin在这个业务模型中如何实现:
coroutineScope.launch {
// 在Dispatchers.IO执行的线程中执行任务1
val async1Result = async(Dispatchers.IO) {
Log.d(TAG, "onCreate: async1 $coroutineContext")
executeTask1()
}
// 在Dispatchers.IO执行的线程中执行任务2
val async2Result = async(Dispatchers.IO) {
Log.d(TAG, "onCreate: async2 $coroutineContext")
executeTask2()
}
// 在调用async方法之后两个协程任务都已经并行跑起来了,这时候调用await方法等待执行结果
val result = async1Result.await() + async2Result.await()
Log.d(TAG, "onCreate: async result = $result")
}
所以总结来说,挂起函数就是切到别的线程,稍后又能够自动切回来的线程调度操作。
挂起函数切到调度器线程中后,是需要协程框架主动调用resumeWith方法再切回来的,如果在非协程非挂起函数调用,那么就没有协程环境,无法切回来,就无法实现挂起的执行逻辑。
协程提供了cancel方法进行取消。
class Job {
public fun cancel(cause: CancellationException? = null)
}
cancel()方法其实还有另外两个重载方法,但是打上了@Deprecated注解,所以不再使用了。