一. 進程,線程,協程 概念
進程與線程
- 進程是資源分配的最小單位,線程是程序執行的最小單位。
- 進程有自己的獨立地址空間,每啓動一個進程,系統就會爲它分配地址空間,建立數據表來維護代碼段、堆棧段和數據段,這種操作非常昂貴。而線程是共享進程中的數據的,使用相同的地址空間,因此CPU切換一個線程的花費遠比進程要小很多,同時創建一個線程的開銷也比進程要小很多。
爲了加大一個應用可使用的內存通過多進程來獲取多份內存空間 通過給四大組件指定android:process屬性可以輕易開啓多進程
- 線程之間的通信更方便,同一進程下的線程共享全局變量、靜態變量等數據,而進程之間的通信需要以通信的方式(IPC-跨進程通信)進行。不過如何處理好同步與互斥是編寫多線程程序的難點。
跨進程通信方式有:
1. 通過Intent(Bundle)附加extras來傳遞信息
2. 通過共享文件來共享數據
3. 採用Binder方式來是想跨進程通信
4. 採用ContentProvider
5. 採用socket
- 但是多進程程序更健壯,多線程程序只要有一個線程死掉,整個進程也死掉了,而一個進程死掉並不會對另外一個進程造成影響,因爲進程有自己獨立的地址空間。
延申到android崩潰同樣道理。
app中大量Web頁面的使用容易導致App內存佔用巨大,存在內存泄露,崩潰率高等問題,WebView獨立進程的使用是解決Android WebView相關問題的一個合理的方案。
線程與協程
kotlin協程是一種用戶態的輕量級線程。一個進程可以擁有多個線程一樣,一個線程也可以擁有多個協程。
協程不是被操作系統內核所管理,而完全是由程序所控制(也就是在用戶態執行)。
協程的開銷遠遠小於線程的開銷。
協程的特點在於是單線程執行,那和多線程比,協程有何優勢?換句話說,協程的出現解決了線程的那些痛點。
- 極高的執行效率:因爲子程序切換不是線程切換,而是由程序自身控制,因此,沒有線程切換的開銷,和多線程比,線程數量越多,協程的性能優勢就越明顯;
- 不需要多線程的鎖機制:因爲只有一個線程,也不存在同時寫變量衝突,在協程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多線程高很多。
小結:
進程:擁有自己獨立的堆和棧,既不共享堆,也不共享棧,進程由操作系統調度; 線程:擁有自己獨立的棧和共享的堆,共享堆,不共享棧,標準線程由操作系統調度; 協程:擁有自己獨立的棧和共享的堆,共享堆,不共享棧,協程由程序員在協程的代碼裏顯示調度。
二. Kotlin協程基本使用
協程主要是讓原來要使用"異步+回調方式"寫出來複雜代碼,簡化成可以用看似同步的方式,這樣我們就可以按串行的思維模式去組織原本分散在不同上下文的代碼邏輯。也增強了程序的可讀性。
//----例如:僞代碼----
launch(Background) {
//執行耗時操作
val bitmap = MediaStore.getBitmap(uri)
launch(UI) {
//更新UI
imageView.setImageBitmap(bitmap)
}
}
集成環境
- 集成kotlin插件
ext.kotlin_version = '1.3.11'
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
- 引入協程核心庫
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.0"
//或使用android
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0"
- experimental啓用聲明
//在module的build.gradle中聲明
kotlin {
experimental {
coroutines 'enable'
}
}
創建
- launch 創建協程
- async 創建帶返回值的協程,返回的是 Deferred 類
- withContext 不創建新的協程,在指定協程上運行代碼塊
- runBlocking 不是 GlobalScope 的 API,可以獨立使用,區別是 runBlocking 裏面的 delay 會阻塞線程,而 launch 創建的不會。
//launch
launch{
delay(1000)
println("launch")
}
//async 有返回值
async {
delay(1000)
println("async")
//async 纔能有return
return@async ""
}
協程上下文-CoroutineContext
- Dispatchers.Default 共享後臺線程池裏的線程
- Dispatchers.Main Android主線程
- Dispatchers.IO 共享後臺線程池裏的線程
- Dispatchers.Unconfined 不限制,使用父Coroutine的現場
- newSingleThreadContext 使用新的線程
/**
* 上下文
*/
launch { // 運行在父協程的上下文中,即 runBlocking 主協程
println("main runBlocking : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) { // 將會獲取默認調度器
println("Default : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Main) { // Android主線程
println("Main : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.IO) { // 共享後臺線程池裏的線程
println("IO : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) { // 不受限的——將工作在主線程中
println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
}
launch(newSingleThreadContext("MyOwnThread")) { // 將使它獲得一個新的線程
println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
}
協程的掛起和恢復
thread 線程之間採取的是競爭 cpu 時間段的方法,誰搶到誰運行,由系統內核控制,對我們來說是不可見不可控的。協程不同,協程之間不用競爭、誰運行、誰掛起、什麼時候恢復都是由我們自己控制的。
- 協程執行時, 協程和協程,協程和線程內代碼是順序運行的。
- 協程掛起時,就不會執行了,而是等待掛起完成且線程空閒時才能繼續執行。
- suspend 修飾的方法掛起的是協程本身。
- await,會阻塞外部協程。適用於多個同級 IO 操作的情況。await()可以返回當前協程的執行結果。
runBlocking {
val deferred1 = async(Dispatchers.Default) {
println(Thread.currentThread())
"hello1"
}
async(Dispatchers.Default){
println(Thread.currentThread())
println("hello2")
//await()可以返回當前協程的執行結果: hello1
println(deferred1.await())
}
}
漫漫開發之路,我們只是其中的一小部分……只有不斷的學習、進階,纔是我們的出路!纔跟得上時代的進步!
今年年初我花一個月的時間收錄整理了一套知識體系,如果有想法深入的系統化的去學習的,可以私信我【學習】,我會把我收錄整理的資料都送給大家,幫助大家更快的進階。