Android沿用了Java的线程模型,一个Android应用在创建的时候会开启一个线程,我们叫它主线程或者UI线程。
如果我们想要访问网络或者数据库等耗时操作,都会开启子线程去处理,从 Android3.0 开始,系统要求网络访问必须在子线程中进行,否则会抛出异常;也就是为了避免主线程被耗时操作阻塞从而产生 ANR。
1. 进程与线程
什么是进程?
进程是操作系统结构的基础,是程序在一个数据集合上运行的过程,是系统进行资源分配和调度的基本单位。进程可以看做是程序的实体,同时,他也是线程的容器。
什么是线程?
线程是操作系统调度的最小单元,也叫轻量级进程。在一个进程中可以创建多个线程,这些线程都拥有各自的计数器,堆栈和局部变量等属性,并且能够访问共享的内存变量。
为什么要使用多线程?
在操作系统级别上来看主要有以下几个方面:
- 使用多线程可以减少程序的响应时间。
- 与进程相比,线程的创建和切换开销更小,同时多线程在数据共享方面效率非常高。
- 使用多线程能简化程序的结构,使程序便于理解和维护。
2.线程的状态
Java的线程运行的声明周期中可能会处于6中不同的状态。
- New 新创建状态。线程被创建,还没有调用Start方法,在线程运行之前还有一些基础工作要做。
- Runnable 可运行状态。一旦调用start方法,线程就处于 Runnable状态。一个可运行的线程可能正在运行也可能没有运行,这取决于操作系统给线程提供运行的时间。
- Blocked 阻塞状态。表示线程被锁阻塞,它暂时不活动。
- Waiting 等待状态,线程暂时不活动,并且不运行任何代码,这消耗最少的资源,直到线程调度器重新激活它。
- Timed waiting 超时等待状态。和等待状态不同的是,它是可以在指定的时间自行返回的。
- Terminated 终止状态。 表示当前线程已经执行完毕。导致线程终止有两种情况: 第一种就是run方法执行完毕正常退出;第二种就是因为没有一个捕获的异常而终止了 run方法,导致线程进入了终止状态。
线程创建后,调用Thread 的 Start方法,开始进入运行状态,当线程执行 wait 方法后,线程进入等待状态,进入等待状态的线程需要其他线程通知才能返回运行状态。超时等待相当于在等待状态加上了时间限制,如果超过时间限制,则线程返回运行状态。当线程调用到同步方法时,如果线程没有获得所则进入阻塞状态,当阻塞状态的线程获取到锁是则重新回到运行状态。当线程执行完毕或者遇到以外异常终止时,都会进入终止状态。
3.创建线程
1.继承Thread类,重写run方法
Thrad本质上也是实现了 Runnable接口的一个实例。需要注意的是调用 start方法后并不是立即执行多线程的代码,而是使该线程变为可运行状态,什么时候运行多线程代码是否操作系统决定的。
public class MyClass extends Thread{ @Override public void run() { System.out.println("Petterp"); } public static void main(String[] args) { MyClass myClass=new MyClass(); myClass.start(); } }
2.实现Runnable接口,并实现该接口的run方法
public class MyClass extends Thread{ @Override public void run() { System.out.println("Petterp"); } public static void main(String[] args) { MyClass myClass=new MyClass(); myClass.start(); } }
3.实现Callable接口,重写call方法
Callable接口是属于Expecutor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能。
- Callable 可以在任务接受后提供一个返回值,Runnable无法提供这个功能。
- Callable 中的call方法可以抛出异常,而Runnable的fun方法不能抛出异常。
- 运行Callable 可以拿到一个 Future的对象,Future对象表示异步计算得到的结果,他提供了检查计算是否完成的方法。由于线程属于异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下就可以使用 Future 来监视目标线程调用 call 方法的情况。但调用 Future的 get() 方法以获取结果是,当前线程就会阻塞,直到 call 方法返回结果。
class TestCallable implements Callable{ public Object call() throws Exception { Thread.sleep(1000); return "Petterp"; } } public class MyClass { public static void main(String[] args) { TestCallable testCallable=new TestCallable(); ExecutorService service= Executors.newCachedThreadPool(); Future future=service.submit(testCallable); try { //等待线程结束,并返回结果 System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
4.中断
当线程的run方法执行完毕,或者在方法中出现没有捕获的异常时,线程将终止。在Java早期版本中有一个Stop方法,其他线程可以调用它终止线程,但是这个方法现在已经被弃用了。interrupt 方法可以用来请求中断线程。当一个线程调用 interrupt 方法时,线程的中断标识位将被置位(中断标识位为 true),线程会不时的检测这个中断标识位,以判断线程是否应该被中断。要想知道线程是否被置位,可以调用Thread.currentThread().inInterrupted()
while(!Thread.currentThread().isIntterrupted()){ }
还可以调用Thread.interrupted() 来对中断标识位进行复位。但是如果一个线程被阻塞,就无法检测中断状态。如果一个线程处于阻塞状态,线程在检查中断标识符是如果发现中断标识位为 true,则会在阻塞方法调用处抛出 InterruptedException 异常,并且在抛出异常前将线程的中断标识位复位,即重新设置为false,需要注意的是被中断的线程不一定会终止,中断线程是为了引起线程的注意,被中断的线程可以决定如何去响应中断,如果是比较重要的线程则不会理会中断,而大部分情况则是线程会将中断作为一个终止的请求。另外,不要在底层代码里捕获 InterruptedExcepetion 异常后不做处理:如下所示
class TestCallable extends Thread{ @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }
可以使用以下两种方式处理异常:
- 在catch 语句中,调用Thread.currentThread.interrupt() 来设置中断状态(因为抛出异常后中断标识符为复位),让外界通过判断 Thread.currentThread().isInterrupted()来决定是否终止线程还是继续下去。
class TestCallable extends Thread{ @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { interrupted(); } } }
2. 更好的做法就是,不适用try来捕获这样的异常,让方法直接抛出,这样调用者可以捕获这个异常,如下
class TestCallable extends Thread { @Override public void run() { } void myTask() throws InterruptedException { sleep(5000); } }
5.安全的终止线程
class TestCallable extends Thread { int i=0; @Override public void run() { System.out.println(i++); } } public class MyClass { public static void main(String[] args) throws InterruptedException { final TestCallable tes=new TestCallable(); tes.start(); TimeUnit.MILLISECONDS.sleep(10); tes.interrupt(); } }
还可以改写成如下写法