什么是线程?
线程或者线程执行本质上就是一串命令(也是程序代码),然后我们把它发送给操作系统执行。
一般来说,我们的CPU在任何时候一个核只能处理一个线程。多核处理器(目前大多数Android设备已经都是多核)顾名思义,就是可以同时处理多线程(通俗地讲就是可以同时处理多件事)。
多核处理与单核多任务处理的实质
上面我说的是一般情况,并不是所有的描述都是一定正确的。因为单核也可以用多任务模拟出多线程。
每个运行在线程中的任务都可以分解成多条指令,而且这些指令不用同时执行。所以,单核设备可以首先切换到线程1去执行指令1A,然后切换到线程2去执行指令2A,接着返回到线程1再去执行1B、1C、1D,然后继续切换到线程2,执行2B、2C等等,以此类推。
这个线程之间的切换十分迅速,以至于在单核的设备中也会发生。几乎所有的线程都在相同的时间内进行任务处理。其实,这都是因为速度太快造成的假象,就像电影《黑客帝国》里的特工Brown一样,可以变幻出很多的头和手。
接下来我们来看一些代码。
Java核心里的线程
在Java中,如果要想做平行任务处理的话,会在Runnable里面执行你的代码。可以继承Thread类,或者实现Runnable接口:
// Version 1
public class IAmAThread extends Thread {
public IAmAThread() {
super("IAmAThread");
}
@Override
public void run() {
// your code (sequence of instructions)
}
}
// to execute this sequence of instructions in a separate thread.
new IAmAThread().start();
// Version 2
public class IAmARunnable implements Runnable {
@Override
public void run() {
// your code (sequence of instructions)
}
}
// to execute this sequence of instructions in a separate thread.
IAmARunnable myRunnable = new IAmARunnable();
new Thread(myRunnable).start();
这两个方法基本上是一样的。第一个版本是创建一个Thread类,第二个版本是需要创建一个Runnable对象,然后也需要一个Thread类来调用它。
第二个版是通常建议使用的方法。这也是一个很大的主题了,超过了本文的范围,以后会再做讨论。
Android上的线程
无论何时启动APP,所有的组件都会运行在一个单独的线程中(默认的)——叫做主线程。这个线程主要用于处理UI的操作并为视图组件和小部件分发事件等,因此主线程也被称作UI线程。
如果你在UI线程中运行一个耗时操作,那么UI就会被锁住,直到这个耗时操作结束。对于用户体验来说,这是非常糟糕的!这也就是为什么我们要理解Android上的线程机制了。理解这些机制就可以把一些复杂的工作移动到其它的线程中去执行。如果你在UI线程中运行一个耗时的任务,那么很有可能会发生ANR(应用无响应),这样用户就会很快地结束掉你的APP。
Android和Java一样,它支持使用Java里面的Thread类来进行一步任务处理。所以可以轻松地像上面Java的例子一样来使用Android上的线程,不过那好像还是有点困难。
为什么在Android上使用标准Java的线程会困难呢?
其实平行任务处理没有想象中的那么简单,你必须在多线程中保证并发,就像伟大的Tim Bray说的那样:ordinary humans can’t do concurrency at scale (or really at all) …
特别对于Android来说,以下这些功能就略显臃肿:
- 异步对于UI线程来说是一个主要的PITA(如果你需要在后台线程中向主线程更新界面,那么你就会用到)。
- 如果屏幕方向或者屏幕配置改变的话,就会出现一些更加奇怪的现象。因为改变屏幕方向,会引起Activity重建(所以后台线程就需要去改变被销毁的Activity的状态了,而如果后台线程不是在UI线程之上的话,那情况会更加复杂,原因如条件1)。
- 对于线程池来说,没有默认的处理方式。
- 取消线程操作需要自定义代码实现。
那么在Android上怎么进行任务并发处理呢?
你可能听过一些Android上一些常见的名词: