Android开发之多线程

来源:互联网 发布:python xgboost 编辑:程序博客网 时间:2024/05/17 08:56

线程与进程的概念

线程在Android中分为主线程与子线程,主线程主要处理和界面相关的事情,而子线程则往往用于执行耗时操作。线程是资源调度的最小单位,系统会给每一个应用程序分配一个虚拟机实例,也就是一个独立的进程,每个应用程序就在它自己的进程中运行,每一个进程可以拥有多个线程。

主线程与子线程的区别

主线程也叫UI线程,主要作用是运行四大组件以及处理他们和用户的交互,子线程的作用则是执行耗时任务。

线程的创建的三种方式

1.继承Thread类创建线程类,重写run()方法,调用start方法启动该线程

public class Main{public static void main(String[] args) {MutliThread mutliThread1 = new MutliThread(100);MutliThread mutliThread2 = new MutliThread(100);MutliThread mutliThread3 = new MutliThread(100);mutliThread1.start();mutliThread2.start();mutliThread3.start();}}
public class MutliThread extends Thread{private int coupon;    MutliThread(int coupon){        this.coupon = coupon;    }        public void run(){        while(coupon > 0){            System.out.println("获取第"+coupon+"张优惠券");            coupon--;        }    }}

程序中定义一个线程类,它继承了Thread类。然后在主类的主方法中创建了三个线程对象,并通过start()方法分别将它们启动。

从结果可以看到,每个线程都有各自的coupon属性,之间并无任何关系,这就说明每个线程之间是平等的,没有优先级关系,因此都有机会得到CPU的处理。

注意:使用继承Thread类的方法来创建线程时,多个线程之间无法共享线程类的实例变量

2.实现runnable接口创建线程类

public class Main{public static void main(String[] args) {MutliThread mutliThread1 = new MutliThread(100);MutliThread mutliThread2 = new MutliThread(100);MutliThread mutliThread3 = new MutliThread(100);new Thread(mutliThread1).start();new Thread(mutliThread2).start();new Thread(mutliThread3).start();}}
public class MutliThread implements Runnable{private int coupon;    MutliThread(int coupon){        this.coupon = coupon;    }public void run() {while(coupon > 0){System.out.println("获取第"+coupon+"张优惠券");coupon--;}}}
注意:Runnable对象仅仅作为Thread的target,Runnable实现类里包含run()方法仅仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。

由于这三个线程也是彼此独立,各自拥有自己的资源,即100张优惠券,因此程序输出的结果和 1 结果大同小异。均是各自线程对自己的100张票进行单独的处理,互不影响。

可见,只要现实的情况要求保证新建线程彼此相互独立,各自拥有资源,且互不干扰,采用哪个方式来创建多线程都是可以的。因为这两种方式创建的多线程程序能够实现相同的功能。

而如果多个线程要共享线程类的实例变量,用实现runnable接口的方法也可以实现
public class Main{public static void main(String[] args) {MutliThread mutliThread = new MutliThread(100);new Thread(mutliThread).start();new Thread(mutliThread).start();new Thread(mutliThread).start();}}
即只需创建一个MutliThread类的实例,将其作为target传入Thread的构造函数中,这样,做个线程里其实用的是同一个target,就可以实现多线程共享线程类的实例变量。不过要注意多线程的安全问题。

我们可以从另一方面即Thread的源码来理解这个问题,由Thread源码可知,Thread类也是继承了runnable接口的实现。

下面是Thread的部分源码

public class Thread implements Runnable {    /* Make sure registerNatives is the first thing <clinit> does. */    private static native void registerNatives();    static {        registerNatives();    }    private volatile String name;    private int            priority;    private Thread         threadQ;    private long           eetop;
Thread类内部实现了runnable接口,每一个Thread类对应一个单独的Runnable,所以我们在用方法1开启多个线程的时候,实际上等于生成了多个runnable实例,所以才会有每个实例的变量各自独立。

3.使用Callable和Future创建线程

通过实现runnable接口创建多线程时,Thread的作用就是把run()方法包装成线程执行体。那么是否可以直接把任意方法都包装成线程执行体呢?答案是否定的,不过从JDK5开始,Java提供了一个Callable接口,该接口很像是Runnable接口的增强版,Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大。
(1).call()方法可以有返回值
(2).call()方法可以声明抛出异常
因此可以提供一个Callable对象作为Thread的target,而该线程的线程执行体则是Callable对象的call()方法。Java5提供了Future接口来代表Callable接口里call()方法的返回值,并未Future接口提供了一个FutureTask实现类,该实现类实现了runnable接口——可以作为Thread的target。

具体实现步骤为:
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
public class Main{public static void main(String[] args) {CallableThread callableThread = new CallableThread();          FutureTask<Integer> futureTask = new FutureTask<>(callableThread);          for(int i = 0;i < 100;i++){             System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);              if(i==20){                  new Thread(futureTask,"有返回值的线程").start();              }          }          try{              System.out.println("子线程的返回值:"+futureTask.get());          } catch (InterruptedException e){              e.printStackTrace();          } catch (ExecutionException e){              e.printStackTrace();          }  }}
public class CallableThread implements Callable<Integer>{@Overridepublic Integer call() throws Exception {// TODO Auto-generated method stubint i = 0;          for(;i<100;i++)          {              System.out.println(Thread.currentThread().getName()+" "+i);          }          return i;  }  }
在上面的程序里,先创建CallableThread对象,然后将其包装成一个FutureTask对象,当主线程执行到i=20的时候,程序启动以FutureTask为target对象为target的线程。程序最后调用FutureTask对象的get()方法来返回call()的返回值——该方法的调用会导致主线程被阻塞,知道call()方法结束并返回为止。

创建线程的三种方式对比

通过继承Thread类或者实现Runnable、Callable接口都可以实现多线程。不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义了方法的返回值,可以声明抛出异常而已。因此可以将二者归结为一种方式,这种方式与继承Thread方式之间的主要差别如下。

采用实现Runnable、Callable接口创建线程的优缺点:
(1)线程类只是实现了其接口,还可以继承其他类
(2)在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好的体现面向对象的思想。
(3)劣势是,编程稍微复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法。

采用继承Thread类的方式创建多线程的优缺点:
(1)优势是,编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程
(2)劣势是,因为线程已经继承了Thread类,所以不能再继承其他父类。
鉴于上面分析,因此一般推荐采用实现Runnable接口、Callable接口的方式来创建多线程。

线程的生命周期

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,它要经过新建(new)、就绪(Runnable)、阻塞(Blocked)和死亡(Dead)5种状态。尤其是当线程启动以后,他不可能一直“霸占”着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。

1、新建和就绪状态

当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他的Java对象一样,仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。

当线程调用start()方法之后,该线程处于就绪状态,Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态的线程并没有开始运行,只是表示程序可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。

2、运行和阻塞状态

如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。如果计算机只有一个CPU,那么在任何时刻只有一个线程处于运行状态。当然,再一个多处理器的机器上,将会有多个线程并行执行;当线程数大于处理器数时,依然会存在多个线程在一个CPU上轮换的现象。

当一个线程开始运行后,它不可能一直处于运行状态,线程的运行过程中需要被中断,目的是使其他线程获得执行的机会,线程调度的细节取决于底层平台所采取的策略。对于采用抢占式策略的系统而言,系统会给每一个可执行的线程一个小时间段来处理任务;当该事件段用完后,系统就会剥夺该线程所占用的资源,让其他线程获得执行机会。在选择下一个线程时,系统会考虑线程的优先级。

当发生如下情况时,线程会进入阻塞状态
(1)线程调用sleep方法主动放弃所占用的系统资源
(2)线程调用一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
(3)线程试图获得一个同步监视器,但更改同步监视器正被其他线程所持有
(4)线程在等待某个通知(notify)
(5)程序调用了线程的suspend方法将线程挂起。不过该方法容易导致死锁,所以程序应该尽量避免使用该方法。

在阻塞状态的线程不能进入就绪队列,只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。

3、死亡状态

线程会以如下三种方式结束,结束后就处于死亡状态:
(1)run()或call()方法执行完成,线程正常结束
(2)线程抛出一个未捕获的Exception或Error
(3)直接调用线程的stop()方法来结束该线程——该方法容易导致死锁,通常不推荐使用