Kotlin-協程(2)-理解非阻塞掛起

協程-理解非阻塞掛起

上一篇,咱們瞭解到了協程的做用,以及協程到底在幹啥,有了對上一篇的瞭解,咱們繼續學協程就稍微的輕鬆了(只能說稍微輕鬆)。java

如何使用協程

如何使用協程呢?git

  1. 協程依賴 Gralde依賴版本查看

注意:Android 和 Java 依賴的庫不同哦,請自行查看。github

  1. 啓動一個協程
fun main() {
    GlobalScope.launch { // 在後臺啓動一個新的協程並繼續
        delay(1000L) // 非阻塞的等待 1 秒鐘(默認時間單位是毫秒)
        println("World! ${Thread.currentThread()}") // 在延遲後打印輸出
    }
    println("Hello, ${Thread.currentThread()}") // 協程已在等待時主線程還在繼續
    Thread.sleep(2000L) // 阻塞主線程 2 秒鐘來保證 JVM 存活
}
複製代碼

上面的代碼是否是一頭霧水,這是官方給咱們提供實例的代碼,是否是看不懂呀?第一次看我也看不懂,那麼接下來忘掉它,接下來看我提供的實例代碼。ide

非阻塞掛起

接下來,咱們來看下 2 段代碼,一個是用 Kotlin 的協程來實現,一個是用 Java 的線程來實現。函數

fun main() {
    // 1.啓動一個協程
    GlobalScope.launch {
        // 輸出當前線程暱稱
        println("1:${Thread.currentThread().name}")
        // 線程等待 1 秒
        delay(1000)
        // 輸出當前線程暱稱
        println("2:${Thread.currentThread().name}")
    }
    // 輸出當前線程暱稱
    println("3:${Thread.currentThread().name}")
    // 阻塞主線程,不讓其中止
    Thread.currentThread().join()
}
輸出結果:

3:main
1:DefaultDispatcher-worker-1
2:DefaultDispatcher-worker-1
複製代碼
public class TestJava {
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("1:" + Thread.currentThread().getName());
                Thread.sleep(1000);
                System.out.println("2:" + Thread.currentThread().getName());

            }
        }).start();
        System.out.println("3:" + Thread.currentThread().getName());
        Thread.currentThread().join();
    }
}
輸出結果:  

3:main
1:Thread-0
2:Thread-0
複製代碼

咱們看下輸出結果是否是感受 2 斷代碼,實現的功能是同樣的,都是開啓另一個線程執行一次等待 1 秒的操做,而後再次輸出線程暱稱。沒錯結果是同樣的,可是真的就同樣嗎?this

接下來咱們就引入協程的一個關鍵 非阻塞式掛起 的概念。spa

要想理解 非阻塞式掛起 咱們先解讀上面的 Java 代碼,咱們首先經過 Thread 啓動了一個線程,此時咱們在線程中調用 Thread.sleep(1000) 時候,此時當前的線程就處於阻塞狀態了。這個線程不能幹其餘事情了,它一直在等待 1 秒後的纔會去執行後續的代碼。線程

可是在實際開發中咱們並不想這樣,例如 AndoridUI 線程,當咱們但願它執行一個耗時操做的時候,也不能阻塞 UI 線程(由於界面渲染也依靠這個線程:ANR 異常),所以咱們的耗時操做只能放在另一個線程中,當耗時操做的線程操做完畢,再通知 UI 線程(這就是地獄回調)。code

接下來,咱們再看 協程 的關鍵代碼。cdn

// 1.啓動一個協程
GlobalScope.launch {
    // 輸出當前線程
    println("1:${Thread.currentThread().name}")
    // 線程等待 1 秒
    delay(1000)
    // 輸出當前線程暱稱
    println("2:${Thread.currentThread().name}")
}
複製代碼

我啓動了一個協程,我使用的 delay(1000) 等待 1 秒,delay 是協程提供的一個 掛起函數,執行掛起函數的意義就是 不阻塞 當前線程,你能夠去幹其餘事,我作完事情了在通知你繼續幫我幹事。

仍是不理解?咱們拿 Android 代碼作一個實例。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        fab.setOnClickListener { view ->
            // 啓動一個協程
            GlobalScope.launch(Dispatchers.Main) {
                Log.d("wyz", "1:${Thread.currentThread().name}  ${System.currentTimeMillis()}")
                delay(2000)
                Toast.makeText(this@MainActivity,"好舒服啊!",Toast.LENGTH_SHORT).show()
                Log.d("wyz", "2:${Thread.currentThread().name}  ${System.currentTimeMillis()}")

            }
        }
        ...
    }
}
輸出結果:  
2019-12-24 10:57:59.767 8917-8917/com.protect.love D/wyz: 1:main  1577156279767
2019-12-24 10:58:01.782 8917-8917/com.protect.love D/wyz: 2:main  1577156281782
複製代碼

注意到個人代碼是在主線程調用的 delay(2000) ,2秒後在主線程彈出了 Toast。你們注意到我主線程一直沒有處於阻塞狀態(由於個人 UI 沒有卡頓)。

這就是協程的 非阻塞式掛起 ,我容許你執行耗時操做,可是我執行耗時我去幹其餘事情。你們注意到協程的 非阻塞式掛起 必需要執行的是一個掛起函數,這個掛起函數的做用就是執行耗時任務,並通知調用線程耗時執行完畢。

到這裏,我想剛開始接觸協程的你們應該和我一開始同樣有點懵逼,爲啥會懵逼呢?大夥想一想這種 非阻塞式掛起 若只使用一個線程,在 JavaDalvikART 虛擬機有可能實現嗎?
實際上是不可能實現的。那爲啥我看似協程代碼只寫了一個線程呢?其實緣由就在 delay(2000) 這個掛起函數,這個掛起函數會開啓另一個線程執行等待 2 秒函數,2 秒後再通知調用的線程,執行後續的代碼。

到這裏,我想你們就應該理解了協程的 非阻塞掛起 的概念了。也應該能推敲出掛起函數要作的事情就是:開啓線程 -> 執行耗時操做 -> 通知調用線程繼續。

補充

這時候在回看我上面的代碼,看似他們實現的功能效果是同樣的。可是 Java 只啓動了一個線程,而協程爲了達到非阻塞式掛起,在調用 delay 函數的時候啓動了另一個線程。

相關文章
相關標籤/搜索