前言
在学习Android
的过程中,我们接触到不少回调 ,bindService( )
成功链接触发的回调ServiceConnection.onServiceConnected( )
、按键点击事件回调方法onClick( )
… 这些回调是通过什么方式实现的?是多线程吗?这些问题始值缠绕在心头。在这里捋一捋,如有理解错误的地方还望指正。
在一个应用系统中,无论使用何种语言开发,必然存在模块之间的调用,调用的方式分为几种:同步调用,异步调用,回调 1。
同步调用
举个简单的例子,我们定义了一个Customer类
,它有一个buybook()
方法调用了Bookstore类
中的getbook()
方法。getbook()
也很简单,直接将书库中的某本书返回即可。
这里若Customer
让出CPU资源
,等待getbook()
执行完再继续执行下去,则我们称为阻塞
;若Customer
不让出资源,而是不断的轮询的getbook()
的结果,则是非阻塞
的。(这种情况,可以确定它们不在同一线程)
这种方法适用于getbook()
耗时不多的情况,如果getbook()
实际执行的结果是:没有该书,现在下单采购…那可的等到天荒地老呀。
这时程序就造成程序阻塞
(注意区分程序阻塞与模块阻塞的区别,若是上文描述的模块非阻塞状况,则不断轮询getbook()
的结果,程序同样会等到地老天荒🤷♂️),buybook()
中后续的代码将不能得到执行。在 Android
中,若在主线程超过阀值 5s 未响应处理,则会造成ANR异常
。
因而在实际情况中,上面的这种非阻塞同步的用处不大,应该说一般不会用到。因为Customer
不阻塞,会一直占用CPU
,但是其实它只是很死脑筋地一次次地去看getbook()
有没有执行完,这将会造成CPU资源
严重浪费 2。
那么如果非得执行较为耗时的操作,需要如何处理呢?
异步调用
异步调用是为了解决同步调用可能出现阻塞,导致整个流程“卡住”而产生的一种调用方式。
还是同步调用的🌰,只是这一次:
buybook()
方法将告诉Bookstore类
:我要一本书!
Bookstore类
:好的。
然后,buybook()
就去干别的事情,接着执行下去了。
但是这样又给我们带来一个新的问题。
例如我们需要在buybook()
中使用到getbook()
返回的书本对象以获取价格,才能…打钱 付款!,由于buybook()
并不会等待getbook()
的执行,因而我们需要使用某些方法对getbook()
进行监听
,以知晓其已执行完成。
在Java
中,可以使用Future
+Callable
的方式做到这一点,具体做法可以参见
Java多线程21:多线程下其他组件之CyclicBarrier、Callable、Future和FutureTask 1 。
看到这可能还是会困惑,你看这个异步,又闷又… 是不是中暑了? 这个异步是怎么实现的呢?
异步的实现
最常见的方式就是多线程
啦。多线程是CPU
抽象出的一个概念,其本质是一段代码,采用分时等机制以维持并发
,所以线程需要操作系统投入CPU资源
来运行和调度。
当然异步
并不是多线程
的专属名称。简单来说,多线程
是实现异步调用的一种方式,但却非全部。DMA
就是一个不错的例子。
所谓DMA
,就是直接内存访问
的意思;也就是说,拥有DMA功能
的硬件在和内存进行数据交换的时候可以不消耗CPU资源
。只要CPU
在发起数据传输时发送一个指令,硬件就开 始自己和内存交换数据,在传输完成之后硬件会触发一个中断来通知操作完成。这些无须消耗CPU
时间的I/O操作
可以成为异步操作
的硬件基础 3。
所以即使在DOS
这样的单进程(而且无线程概念)系统中也同样可以发起异步
的DMA操作
。引进DMA
可以提高处理器
与I/O处理器
的并行度
3。
此外,我们开启硬件定时器也是一种异步操作,这种异步操作带着回调 – 溢出中断
,以告知我们计数结果。多核CPU
带来的并行
处理也是一种异步操作。
总结一下我已知的异步实现:
- 软件上:多线程
- 硬件上:多核CPU、DMA,硬件定时器
回调
前面异步调用
说到异步带来的问题,并给出了Java
中解决的方法;而更常见的实现是回调
。回调其实并不依赖于任何一种语言,它是一种思想,一种机制 1。
还是那个🌰:
buybook()
方法将告诉Bookstore类
:我要一本书!
Bookstore类
:好的,你