一、说明
Android沿用了JAVA的线程模型,一个Android应用在创建的时候会开启一个线程,我们叫它主线程或者UI线程,如果我们想访问网络或者数据库等耗时操作时,都会开启子线程去处理,从Android3.0开始,系统要求网络访问必须在子线程中进行,否则会抛出异常,也就是为了避免主线程被耗时操作阻塞而产生ANR。
二、进程与线程
(1)、什么是进程
进程是操作系统结构的基础,是程序在一个数据集合上运行的过程,是系统进行资源分配和调度的基本单位。进程可以被看作程序的实体,同样,也是线程的容器。
(2)、什么是线程
一个程序运行多个功能,这些个功能对应一个任务,这些子任务就是线程,是操作系统调用的最小单元,也叫做轻量级进程。在一个进程中可以创建多个线程,这些线程都拥有各自的计数器、堆栈、和局部变量等属性,能够访问共享的内存变量。
(3)、为何要使用多线程
- 使用多线程可以减少程序的响应时间,如果某个操作很耗时,或者陷入长时间的等待,此时程序将不会响应事件的操作(鼠标,键盘的操作,APP滑动,点击操作),使用多线程后可以把这个耗时的操作分配到一个单独的线程中去执行,从而使程序具备更好的交互。
- 与进程相比,线程创建和切换开销更小,同时多线程在数据共享方面效率非常高。
- 多CPU或者多核计算机本身就具备执行多线程的能力,如果使用单个线程,将无法重复利用计算机资源,在多CPU计算机中使用多线程能提高CPU的利用率
- 使用多线程能简化程序的结构,使程序便于维护和结偶。
三、线程的状态(6种)
- New : 新创建的状态。线程被创建,但是还没有调用start方法,在线程运行之前还有一些基础工作要做。
- Runnable:可运行状态,一旦调用start方法,线程就处于Runnable状态,一个可运行的线程可能正在运行也可能没有运行,这取决于操作系统给线程提供运行的时间。
- Blocked:堵塞状态,表示线程被锁堵塞,暂时不能活动。
- Waiting:等待状态,线程暂时不能活动,并且不运行任何代码,这消耗最少的资源,直到线程调度器再次重新激活。
- Timed waiting:超时等待状态。和等待状态不同的是,它是可以在指定的时间自行返回的。
- Terminated:终止状态,表示当前线程已经执行完毕,导致线程终止有两种情况:第一就是run方法执行完毕正常退出,第二种就是因为一个没有捕获的异常而终止了run方法,导致线程进入终止状态。
线程创建后,调用Thread的start方法,开始进入运行状态,当线程执行wait方法后,线程进入等待状态,进入等待状态的线程需要其他线程通知才能返回运行状态(线程的唤醒)。超时等待相当于在等待状态加上了时间限制,如果超过时间限制,则线程返回运行状态。当线程调用到同步方法时,如果线程没有获得锁则进入阻塞状态,当阻塞状态的线程获取到锁时则重新回到运行状态。当线程执行完毕或者遇到意外异常终止时,则会进入终止状态。
四、创建线程
1、继承Thread类,重写run()方法
Thread本质上也是实现了Runnable接口的一个实例。需要注意的是调用start()方法后并不是立即的执行多线程的代码,而是使该线程变为可运行状态,什么时候运行多线程是由操作系统决定的。
实现方法
- 定义Thread的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。run方法体就是执行体。
- 创建Thread子类的实例,即创建了线程对象。
- 调用线程对象的start()方法来启动线程。
public class ThreadTest {
//定义一个Thread的子类 重写run方法
public static class MyThread extends Thread{
@Override
public void run() {
super.run();
//需要执行的逻辑代码
System.out.println("MyThread is running");
}
}
public static void main(String[] args) {
Thread myThread = new MyThread();
myThread.start();
}
}
2、实现Runnable接口,并实现该接口的run()方法
实现方法
- 自定义类并实现Runnable接口,实现run()方法
- 创建Thread子类的实例,用实现Runnable接口的对象作为参数实例化该Thread的对象。
- 调用Thread的start()方法来启动线程。
public class RunnableTest {
//自定义一个类,并实现Runnable接口,实现run()方法
public static class TestRunnable implements Runnable{
@Override
public void run() {
//需要执行的逻辑代码
System.out.println("TestRunnable is running");
}
}
public static void main(String[] args) {
TestRunnable mRunnable = new TestRunnable();
//创建Thread的实例,实现Runnable接口的对象作为参数
Thread mThread = new Thread(mRunnable);
//启动线程
mThread.start();
}
}
3、实现Callable接口,重写call()方法
Callable接口实际是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能,主要表现为:
- Callable可以在任务接收后提供一个返回值,Runnable无法提供这个功能。
- Callable中的call()方法可以抛出异常,而Runnable的run()方法不能抛出异常。
- 运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供了检查计算是否完成的方法。由于线程属于异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下就可以使用Future来监视目标线程调用call()方法的情况,但调用Future的get()方法以获取结果时,当前线程就会阻塞,知道call()方法返回结果。
import java.util.concurrent.*;
public class CallableTest {
//创建线程类
public static class MyTestCallable implements Callable{
@Override
public Object call() throws Exception {
return "MyTestCallable is running";
}
}
public static void main(String[] args) {
MyTestCallable myTestCallable = new MyTestCallable();
ExecutorService mExecutorService = Executors.newCachedThreadPool();
Future mFuture = mExecutorService.submit(myTestCallable);
try {
System.out.println(mFuture.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
在这3种方法中,一般推荐使用实现Runnable接口的方式,原因在于一个类应该在其需要加强或者修改时才会被继承,因此如果没有必要重写Thread类的其他方法,在这种情况下最好用实现Runnable接口的方式。
五、理解中断
当线程的run方法执行完毕,或者在方法中出现没有捕获的异常时,线程将终止。 在JAVA 早起版本中有一个stop()方法。其他线程可以调用它终止线程, 但是这个方法现在已经被弃用,interrupt()方法可以用来请求中断线程。当一个线程调用interrupt()方法时,线程的中断标识位将被置位(中断标识位true),线程会不时地检测这个中断标识位,以判断线程是否应该被中断。要想知道线程是否被置位,可以调用Thred.currentThread.isInterrupt()
while (!Thread.currentThread().isInterrupted()){
//TODO 执行体
}
还可以调用Thread. intrerrupted()来对中断标识位进行复位。但是如果一个线程被阻塞, 就无法检测中断状态。如果一个线程处于阻塞状态,线程在检查中断标识位时如果发现中断标识位为true,则会在阻塞方法调用处抛InterruptedException异常,并且在抛出异常前将线程的中断标识位复位,即重新设置为false。需要注意的是被中断的线程不一定会终止,中断线程是为了引起线程的注意,被中断的线程可以决定如何去响应中断。如果是比较重要的线程则不会理会中断,而大部分情况则是线程会将中断作为一一个终 止的请求。另外, 不要在底层代码里捕
获intreruptedException异常后不做处理:
void myTask () {
...
try {
sleep(50);
} catch (InterruptedException e) {
}
...
}
如果你不知道抛出IterruptedException 异常后如何处理,这里介绍两种合理的处理方式,
(1)在catch子句中,调用Thread.currentThread.interrupt()来设置中断状态(因为抛出异常后中断标识位会复位),让外界通过判断Thread.currentThread.isInterrupted()来决定是否终止线程还是继续下去,应该这样做:
void myTask () {
...
try {
sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().isInterrupted();
}
...
}
}
(2)更好的做法就是,不使用try来捕获这样的异常,让方法直接抛出,这样调用者可以捕获这个异常。
void myTask() throw InterruptedException{
sleep(50);
}
六、安全地终止线程
使用中断终止线程
import java.util.concurrent.TimeUnit;
public class StopThread {
public static void main(String[] args) throws InterruptedException {
MoonRunner moonRunner = new MoonRunner();
Thread mThread = new Thread(moonRunner);
mThread.start();
TimeUnit.MILLISECONDS.sleep(10);
mThread.interrupt();
}
public static class MoonRunner implements Runnable{
private long i;
@Override
public void run() {
while (! Thread.currentThread().isInterrupted()){
i++;
System.out.println("i = "+ i);
}
System.out.println("Stop");
}
}
}
在上面代码中调用了TimeUnit.MILLISECONDS.sleep(10)使得主线程睡眠10MS,这是为了留给MoonThread线程时间来感知中断从而结束。
使用boolean变量控制是否需要停止线程
import java.util.concurrent.TimeUnit;
public class StopThread {
public static void main(String[] args) throws InterruptedException {
MoonRunner moonRunner = new MoonRunner();
Thread mThread = new Thread(moonRunner);
mThread.start();
TimeUnit.MILLISECONDS.sleep(10);
// mThread.interrupt();
moonRunner.cancle();
}
public static class MoonRunner implements Runnable{
private long i;
private volatile boolean on = true;
@Override
public void run() {
// while (! Thread.currentThread().isInterrupted()){
// i++;
// System.out.println("i = "+ i);
// }
// System.out.println("Stop");
while (on){
i++;
System.out.println("i = "+ i);
}
System.out.println("Stop");
}
public void cancle(){
on = false;
}
}
}
上面的代码使用了volatile关键字,因为设计多个线程对这个变量的访问,所以当我们在设置volatile boolean on的时候,当有其他线程去改变他的值时,所有的线程都会感知到他的变化。
基础部分大概就这些,这几天会陆续更新关于线程的知识,包括线程的同步,线程队列,线程池,还有你留言想要了解的…