上一篇,咱们了解到了协程的做用,以及协程到底在干啥,有了对上一篇的了解,咱们继续学协程就稍微的轻松了(只能说稍微轻松)。java
如何使用协程呢?git
注意:Android 和 Java 依赖的库不同哦,请自行查看。github
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
秒后的才会去执行后续的代码。线程
可是在实际开发中咱们并不想这样,例如 Andorid
的 UI
线程,当咱们但愿它执行一个耗时操做的时候,也不能阻塞 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
没有卡顿)。
这就是协程的 非阻塞式挂起
,我容许你执行耗时操做,可是我执行耗时我去干其余事情。你们注意到协程的 非阻塞式挂起
必需要执行的是一个挂起函数,这个挂起函数的做用就是执行耗时任务,并通知调用线程耗时执行完毕。
到这里,我想刚开始接触协程的你们应该和我一开始同样有点懵逼,为啥会懵逼呢?大伙想一想这种
非阻塞式挂起
若只使用一个线程,在Java
丶Dalvik
丶ART
虚拟机有可能实现吗?
实际上是不可能实现的。那为啥我看似协程代码只写了一个线程呢?其实缘由就在delay(2000)
这个挂起函数,这个挂起函数会开启另一个线程执行等待2
秒函数,2
秒后再通知调用的线程,执行后续的代码。
到这里,我想你们就应该理解了协程的 非阻塞挂起
的概念了。也应该能推敲出挂起函数要作的事情就是:开启线程 -> 执行耗时操做 -> 通知调用线程继续。
这时候在回看我上面的代码,看似他们实现的功能效果是同样的。可是 Java
只启动了一个线程,而协程为了达到非阻塞式挂起,在调用 delay
函数的时候启动了另一个线程。