Android多线程编程知识点总结

Android 3.0开 始,系统要求网络访问必须在子线程中进行,否则会抛出ANR(Application Not Responding);

什么是进程?

抽象的定义:进程是操作系统结构的基础,是程序在一个数据集合上运行的过程,是系统进行资源分配和调度的基本单位。

形象的定义:进程是一个程序的实体,一个应用程序可以看做一个进程,进程是线程的容器。

什么是线程?

线程是操作系统调度的最小单元,也叫轻量级进程。

在一个进程中可以创建多个线程,每个线程又都拥有各自的计数器、堆栈、局部变量等属性,都能访问共享内存变量。

为什么要用到多线程?

  • 避免ANR:Activity是5s、BroadcastReceiver是10s、Service是20s
  • 开销小,数据共享效率高:与进程相比,线程的创建和切换开销更小,且多线程在数据共享方面效率很高
  • 避免资源浪费:多核手机本身就具备执行多线程的能力,如果只用单线程就会造成资源浪费
  • 简化程序结构,使程序便于理解和维护

线程都有哪些状态(生命周期)?

  • New:新建状态。线程被创建,还没有调用start方法,在线程运行之前做一些基础工作
  • Runnable:就绪状态,也叫可运行状态。调用了start方法后进入就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态的线程并没有开始运行,它只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。
  • Blocked:阻塞状态。表示线程被阻塞,暂时不活动
  • Waiting:等待状态。线程暂时不活动,并且不运行任何代码,消耗最少的资源,直到线程调度器重新激活它
  • Timed Waiting:超时等待状态。不同于等待状态,它可以在指定时间内自行返回
  • Terminated:终止状态。表示当前线程已经执行完毕。导致终止状态有两种情况:一是run方法执行完毕正常退出;二是在程序执行过程中抛出了没有被捕获的异常

如何实现多线程?

  1. 继承Thread类,重写run方法
  2. 实现Runnable接口,并实现其run方法
  3. 实现Callable接口,重写call方法。相较于Runnable,Callable更强大,例如:Callable可以提供返回值;call()方法可以抛出异常,run()不行;Callable提供了检查计算是否完成的Future对象;

不过我们最常用的这种:匿名类的方式

new Thread(new Runnable() {
            @Override
            public void run() {
                //TODO
            }
        }).start();

小结:一般推荐Runnable方式,原因:一个类(Thread)在其需要扩展或修改时才需要被继承,故,若无需扩展或修改,则使用直接实现Runnable接口的匿名类更为简洁方便。

线程中断:①.调用thread.interrupt()方法;②.设置标记位(如用boolean标记)跳出耗时操作,直接结束run方法。

在线程的run方法执行完毕或抛出未捕获的异常时则会结束或中断。之前java有个已经被弃用的stop方法来中断线程。

interrupt:当一个线程调用 interrupt 方法时,线程的中断标识位将被置位(中断标识位为true),线程会不时地检测这个中断标识位,以判断线程是否应该被中断。要想知道线程是否被置位,可以调用Thread.currentThread().isInterrupted()。

  • 如果一个线程处于阻塞状态,线程在检查中断标识位时如果发现中断标识位为true,则会在阻塞方 法调用处抛出InterruptedException异常,并且在抛出异常前将线程的中断标识位复位,即重新设置为false
  • 被中断的线程不一定会终止,中断线程是为了引起线程的注意,被中断的线程可以决定如何去响应中断。如果是比较重要的线程则不会理会中断,而大部分情况则是线程会将中断作为一个终止的请求
  • 不要在底层代码里捕获InterruptedException异常后不做处理(在catch里复位、将异常向上抛出,不用try)

线程同步——处理竞争条件

  • ReentrantLock:重入锁,支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。
  • Condition:条件对象,得到条件对象后调用await方法,当前线程就被阻塞了并放弃了锁。
        boolean flag = true; //设置临界条件
        Lock lock = new ReentrantLock(); //新建重入锁
        lock.lock();
        Condition condition = lock.newCondition(); //获取条件对象
        try {
            while (flag) {
                //释放锁,阻塞当前线程
                condition.await();
            }
            condition.signalAll(); //解除等待线程的阻塞状态
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

synchronized:java中每一个对象都有一个内部锁。如果一个方法用 synchronized 关键字声明,那么对象的锁将保护整个方法。也就是说,要调用该方 法,线程必须获得内部的对象锁。

public synchronized void method(){
    ...
}

等同于

Lock mLock = new ReentrantLock();
public void method(){
    mLock.lock();
    try{
     ...
    }catch(){
    }finally{
        mLock.unlock();
    }
}

同步代码块:synchronized(Object){},目的是为了使用Object类的内部锁

小结:同步代码块是非常脆弱的, 通常不推荐使用。一般实现同步最好用java.util.concurrent包下提供的类,比如阻塞队列。如果同步方法适合 你的程序,那么请尽量使用同步方法,这样可以减少编写代码的数量,减少出错的概率。如果特别需要使 用Lock/Condition结构提供的独有特性时,才使用Lock/Condition。

volatile:①.将所声明的变量提升到主内存,使该变量的读写操作对其他线程是立即可见的;

                ②.禁止指令重排:当程序执行到volatile变量的操作时,在其前面的操作已经全部执行完毕,并且结果会对后面的操作可见,在其后面的操作还没有进行;在进行指令优化时,在volatile变量之前的语句不能在volatile变量后面执行,同样,在volatile变量之后的语句也不能在volatile变量前面执行。

                ③.不保证原子性;

并发编程中的3个特性:原子性、可见性和有序性。

阻塞队列——生产者、消费者

1.队列中无数据,消费者端所有线程自动阻塞挂起,直到有数据入队列;

2.队列中数据满,生产者所有线程自动阻塞挂起,直到队列中有空闲位置;

• ArrayBlockingQueue:由数组结构组成的有界阻塞队列。

• LinkedBlockingQueue:由链表结构组成的有界阻塞队列。

阻塞队列原理:借助了重入锁和条件对象

//源码中的取数据方法
public E take() throws InterruptedException {
        
        //重入锁
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await(); //条件阻塞
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

//源码中的存数据方法
public void put(E e) throws InterruptedException {
        Objects.requireNonNull(e); //非空判断
        final ReentrantLock lock = this.lock; //重入锁
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await(); //条件阻塞
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

线程池

处理流程:

  1. 判断是否达到核心线程数,未达到则创建核心线程,否则进入下一步;
  2. 判断任务队列是否已满,未满则加入任务队列,否则进入下一步;
  3. 判断是否达到最大线程数,未达到则创建非核心线程处理任务,否则执行包和策略,默认抛异常。                  

常用线程池

  • FixedThreadPool:只有固定数量核心线程,内部的任务队列采用LinkedBlockingQueue无界的阻塞队列;
  • CachedThreadPool:只有非核心线程,采用SynchronousQueue不存储元素的阻塞队列,每个插入操作 必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。适于大量的需要立即处理并且耗时较少的任务。
  • SingleThreadExecutor:只有一个核心线程,确保所有的任务在一个线程中按照顺序逐一执行。
  • ScheduledThreadPool:能实现定时和周期性任务的线程池

AsyncTask的原理

      AsyncTask原理分析

猜你喜欢

转载自blog.csdn.net/S_Alics/article/details/101289549