进程:是应用程序启动实例。比如运行一个游戏,打开软件,就开启了一个进程。进程拥有代码和打开文件资源、数据资源、独立的内存空间。
线程:从属于进程,是程序的实际执行者。一个进程至少包含一个主线程,也可以有更多的线程,线程有自己的栈空间。
对操作系统:线程是最小的执行单位,进程是最小的资源管理单元。无论是进程还是线程,都由操作系统管理。
Java中线程具有五种状态:
初始化
可运行
运行中
阻塞
销毁
JVM需要通过操作系统内核中的TCB(Thread Control Block)模块来改变线程的状态,需要消耗一定的CPU资源。
进程和线程协作:生产者/消费者模式。
消耗性能的操作:
1、同步锁
2、线程阻塞状态和可运行状态切换
3、线程上下文的切换
协程(Coroutines)使用场景
高负荷网络IO、文件IO、CPU/GPU密集型任务。
协程是轻量级线程
比线程更加轻量级的存在。一个线程可以拥有多个协程。
协程不是被操作系统内核管理,完全是程序控制(用户态执行)。不会像线程切换那样消耗资源。
协程应用:
Lua语音5.0开始通过扩展库coroutine实现
python通过yield/send实现协程,python3.5以后async/await成为最好替代方案
Go语言协程强大简洁,成百上千协程并发。
Java语言没有对协程原生支持,开眼框架模拟出协程Kilim框架
启动100,000个协程,每秒打印一个点。全部在[main,5,main]中耗时4.5s左右打印完成。内存波动比较小(占内存小)
启动相同个数的线程(大多数情况下会内存溢出错误),在Thread[Thread-2-->Thread-100001,5,main]即使全部打印完成需要2.3min左右。内存波动比较大(占内存大)
如果换做
fun main() = runBlocking {
repeat(100_000) { // 启动大量的协程
launch {
delay(1000L)
print(".")
}
}
}
GlobalScope.launch { …… } 替换 thread { …… }
用 delay(……) 替换 Thread.sleep(……)
delay是特别的挂起函数,不会造成线程阻塞,挂起函数只能在协程中使用
Thread.sleep()是阻塞等待
GlobalScope.launch{}大括号中所在线程:Thread[DefaultDispatcher-worker-1,5,main]。属于非主线程
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(1000L) // 无阻塞的等待1秒钟(默认时间单位是毫秒)
println("测试-World!-${Thread.currentThread()} ${Looper.getMainLooper() == Looper.myLooper()}") // 在延迟后打印输出
}
println("测试-Hello,") // 主线程的协程将会继续等待
Thread.sleep(2000L) // 阻塞主线程2秒钟来保证 JVM 存活
桥接阻塞和非阻塞
runBlocking:显式的阻塞
runBlocking{}线程名:Thread[main,5,main]属于住线程
GlobalScope.launch {
// 在后台启动一个新的协程并继续
delay(1000L) // 无阻塞的等待1秒钟(默认时间单位是毫秒)
println("测试-World!-${Thread.currentThread()} ${Looper.getMainLooper() == Looper.myLooper()}") // 在延迟后打印输出
}
println("测试-Hello,") // 主线程的协程将会继续等待
runBlocking { // 阻塞……我们延迟2秒来保证 JVM 的存活
delay(2000L)
}
惯用方法
fun main() = runBlocking<Unit> {
GlobalScope.launch {
delay(1000L)
println("${TAG}-${Thread.currentThread()}-world!")
}
println("${TAG}-${Thread.currentThread()}-hello,")
delay(2000L)
}
等待一个任务
fun main() = runBlocking<Unit> {
val job = GlobalScope.launch { // 启动一个新的协程并保持对这个任务的引用
delay(1000L)
println("${TAG}-${Thread.currentThread()}-world")
}
println("${TAG}-${Thread.currentThread()}-hello,")
job.join() // 等待直到子协程执行结束
}
结构化并发
由GlobalScope.launch改为launch
这里两次打印所在的线程:Thread[main,5,main]都在主线程中
猜测:这个结构化并发是阻塞的
验证的确是阻塞的:launch竟然在主线程中,尝试了下在launch中的delay延迟8s,在activity的onCreate或者onResume中调用。竟然阻塞了ui(actionBar、TextView中的内容竟然8s后才显示)
fun main() = runBlocking {
launch {
// 启动一个新的协程并保持对这个任务的引用
delay(1000L)
println("${TAG}-${Thread.currentThread()}-world")
}
println("${TAG}-${Thread.currentThread()}-hello,")
}
作用域构建器
fun main() = runBlocking { // this: CoroutineScope
// main@coroutine#1
launch {
// main @coroutine#2
delay(200L)
println("${Thread.currentThread()}-Task from runBlocking")
}
coroutineScope { // 创建一个新的协程作用域
// main @coroutine#1
launch {
// main @coroutine#3
delay(500L)
println("${Thread.currentThread()}-Task from nested launch")
}
delay(100L)
println("${Thread.currentThread()}-Task from coroutine scope") // 该行将在嵌套启动之前执行打印
}
// main@coroutine#1
println("${Thread.currentThread()}-Coroutine scope is over") // 该行将在嵌套结束之后才会被打印
}
提取函数重构
suspend:修饰会被暂停的函数,只能用着协程或其他suspend函数当中
fun main() = runBlocking {
launch { doWorld() }
println("${Thread.currentThread()}-Hello,")
}
// 你的第一个挂起函数
suspend fun doWorld() {
delay(1000L)
println("${Thread.currentThread()}-World!")
}
像守护线程一样的全局协程
如下代码:会输出三次:0、1、2
GlobalScope.launch {
repeat(1000) { I ->
println("I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // 在延迟之后结束程序