Android 多线程面试

Android程序中默认只有一个进程,一个进程里面可以包含多个线程。

多线程 - 介绍

定义:多个线程同时进行,即多个任务同时进行。

注意:

  1. 其实,计算机任何特定时刻只能执行一个任务;
  2. 多线程只是一种错觉:只是因为JVM快速调度资源来轮换线程不断轮流执行,所以看起来好像在同时执行多个任务而已。

一、Android中线程分类及作用

1.1 按用途分类     

  • 主线程:

       定义:Android系统在程序启动时会自动启动一条主线程

       作用:处理四大组件与用户进行交互的事情(如UI、界面交互相关)

       注:因为用户随时会与界面发生交互,因此主线程任何时候都必须保持很高的响应速度,所以主线程不允许进行耗时操作,                否则会出现ANR

  • 子线程:

        定义:一个基本的CPU执行单元 & 程序执行流的最小单元。

        作用:处理耗时操作,比如网络请求,复杂计算等,减少程序在并发执行时所付出的时空开销,提高操作系统的并发性能。

        子线程的状态如图所示:

        

----------------------------------------------------------Thread-------------------------------------------------------------

2.2 按形态分类:

一、Thread

       1、说明: 基本的线程,可以做一些简单的操作,经常配合Handler使用。

       2、Thread主要函数

             run():线程运行时所执行的代码

             start():启动线程

             sleep():线程休眠,进入阻塞状态,sleep方法不会释放锁

             yield():线程交出CPU,但是不会阻塞而是重置为就绪状态,不会释放锁

             interrupt():中断线程,注意只能中断阻塞状态的线程

             setDaemon():设置和获取是否守护线程

      3、Thread的几种状态

            新建状态(new):实例化之后进入该状态;

            就绪状态(Runnable):线程调用start()之后就绪状态等待cpu执行,注意这时只是表示可以运行并不代表已经运行;

            运行状态(Running):线程获得cpu的执行,开始执行run()方法的代码;

            阻塞状态(Blocked):线程由于各种原因进入阻塞状态:join()、sleep()、wait()、等待触发条件、等待由别的线程占用的锁;

            死亡状态(Dead):线程运行完毕或异常退出,可使用isAlive()获取状态。

      4、Android中Thread的使用

           1.1、继承Thread,重写run()方法。

           1.2、实现Runnable,重写run()方法来执行任务

           1.3、通过Handler启动线程

      5、如何终止线程

            1.1、使用boolean变量作为标记

            1.2、使用interrupt()

       线程正常运行状态: 中断线程而不是结束线程。

                    线程阻塞状态:抛出异常,利用这个异常来跳出循环。

            1.3、使用stop()方法终止线程

                 这个相当于强制关机,万一数据丢失得不偿失。

      6、线程安全与线程同步

            1.1、什么是线程安全问题?
             简单地说,线程安全问题是指多个线程访问同一代码或数据,造成结果和数据的错乱。

            1.2、解决的方法:

               (1)synchronized 关键字,保证同时刻只有一个线程进入该方法或者代码块。

               (2)volatile特殊域变量修饰变量:告诉虚拟机该变量随时可能更新,因此使用时每次都会重新计算,而不是使用寄存器的值。

               (3)ReentrantLock: 使用重入锁实现线程同步。

               (4)ThreadLocal管理变量:每一个使用该变量的线程都会获得该变量的副本,副本之间相互独立。

---------------------------------------------------AsyncTask--------------------------------------------------------------

二、AsyncTask

       1、说明:轻量级的异步操作类,方便更新UI。

       2、原理:封装了Handler和两个线程池。

            (1)线程池THREAD_POOL_EXECUTOR:正真执行线程的任务

            (2)线程池SERIAL_EXECUTOR:任务调度(让多个线程任务 按顺序排列)

            (3)AsyncTask创建时会实例化一个WorkerRunnable对象mWorker和一个FutureTask对象mFuture。FutureTask是一个并                  发类,充当Runnable的作用。接着会串行排队并使用工作线程来处理实际任务。

            (4)FutureTask对象创建时把WorkerRunnable对象作为参数传递,在FutureTask在执行任务run()时会调用                                          WorkerRunnable的call()方法,因此call()方法是在线程池中进行的。

            (5)主线程的Handler:

  • 调用了call()方法后,调用doInBackground并返回结果。
  • 这个过程中任务被取消会catch跳出设置AsyncTask结束。
  • call()方法最后postResult(result)。
  • 获取主线程的Handler,进行工作线程和主线程的通信

   3、参数以及核心函数

   AsyncTask还提供了4个核心方法:

  • protected void onPreExecute():在主线程执行,异步任务执行前调用做准备工作;
  • protected abstract Result doInBackground(Params... var1):在线程池中执行,用于执行异步任务。Params表示异步任务输入参数,可以在此方法中通过publishProgress方法来更新任务进度,publishProgress方法又调用onProgressUpdate方法实现主线程进度更新。这个方法返回ResultonPostExecute方法。
  • protected void onProgressUpdate(Progress... values):在主线程执行,后台任务执行进度发生变化会调用此方法。
  • protected void onPostExecute(Result result):在主线程执行,异步任务执行后调用,result参数是由doInBackground返回的。

  4、AsyncTask使用注意

  • AsyncTask的对象、类必须在主线程创建,加载。
  • execute方法必须在UI线程调用。
  • 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则报运行时异常。

  5、 优点

  1. 简单快捷,使用方便。
  2. UI更新及时,过程可控。

  6、缺点

    1、多个异步操作需要更新UI时,就变得麻烦起来。

---------------------------------------------------HandlerThread-----------------------------------------------------

三、HandlerThread

       1、说明:一个使用了Looper、Handler的线程。

       2、 原理

               (1)继承了Thread,实际上是一个使用Looper、Handler的线程。

               (2)继承了Thread,在run()方法中通过Looper.prepare()来创建消息队列,Looper.loop()来循环处理消息。

               (3)使用时开启HandlerThread,创建Handler与HandlerThread的Looper绑定,Handler以消息的方式通知HandlerThread来执行一个具体的任务。

               (4)HandlerThread内部维护了一个消息队列,避免多次创建和销毁子线程来进行操作。

      3、用例:

//创建HandlerThread实例,参数字符串定义新线程的名称。
HandlerThread mHandlerThread = new HandlerThread("check-message-coming");

//启动HandlerThread线程。
mHandlerThread.start(); 

//Handler与HandlerThread绑定
Handler mCheckMsgHandler = new Handler(mHandlerThread.getLooper()){
     @Override
     public void handleMessage(Message msg){
         // 进行耗时操作
     }
};

注意:要记得在onPause()onDestroy()中暂停更新和停止mHandlerThread以释放内存。

-----------------------------------------------------IntentService-----------------------------------------------------

四、IntentService

      1、说明:IntentService封装了HandlerThread和一个Handler,IntentService封装了HandlerThread和一个Handler。

      2、原理:

  • IntentService创建时启动一个HandlerThread,同时将Handler绑定HandlerThread。所以通过Handler发送的消息都在HandlerThread中执行。
  • 然后IntentService进入生命周期onStartCommand再调用onStart将传进的Intent对象以消息的形式使用Handler发送。
  • Handler收到消息后会调用onHandleIntent这样一个抽象方法,这个方法需要我们自己实现去处理逻辑。最后处理完毕stopSelf(msg.arg1);等待所有任务完成结束IntentService;

-------------------------------------------------------线程池-------------------------------------------------------------

五、线程池

     1、原理:   

           Android中的线程池概念来源于Java中的Executor,Executor是一个接口,真正的线程的实现为ThreadPoolExecutor。                (ThreadPoolExecutor继承了AbstractExecutorService,AbstractExecutorService是ExecutorService的实现类,                            ExecutorService继承了Executor接口)。

      2、优点:

  1. 重用线程池中的线程,避免频繁创建和销毁线程所带来的内存开销。
  2. 有效控制线程的最大并发数,避免因线程之间抢占资源而导致的阻塞现象。
  3. 能够对线程进行简单的管理,提供定时执行以及指定时间间隔循环执行等功能。

     3、线程池的分类

      (1)、 FixedThreadPool (Fixed:固定的,不变的)    

      通过Executors的newFixedThreadPool创建,通过创建时的参数可以看出又以下几个特点:

  • 线程数量固定且都是核心线程:核心线程数量和最大线程数量都是nThreads;
  • 都是核心线程且不会被回收,快速相应外界请求;
  • 没有超时机制,任务队列也没有大小限制;
  • 新任务使用核心线程处理,如果没有空闲的核心线程,则排队等待执行。

      (2)、CachedThreadPool (Cached:缓存)

       通过Executors的newCachedThreadPool创建,特点:

  • 线程数量不定,只有非核心线程,最大线程数任意大:传入核心线程数量的参数为0,最大线程数为Integer.MAX_VALUE;
  • 有新任务时使用空闲线程执行,没有空闲线程则创建新的线程来处理。
  • 该线程池的每个空闲线程都有超时机制,时常为60s(参数:60L, TimeUnit.SECONDS),空闲超过60s则回收空闲线程。
  • 适合执行大量的耗时较少的任务,当所有线程闲置超过60s都会被停止,所以这时几乎不占用系统资源。

     (3)、ScheduledThreadPool(Scheduled:预定的、排定的)

       通过Executors的newScheduledThreadPool创建,特点:

  • 核心线程数量固定,非核心线程数量无限制
  • 非核心线程闲置超过10s会被回收;
  • 主要用于执行定时任务和具有固定周期的重复任务;
  • 四个里面唯一一个有延时执行和周期重复执行的功能

    (4)、SingleThreadExecutor (单线程线程池)
       通过Executors的newSingleThreadExecutor创建,特点:

  • 只有一个核心线程,所有任务在同一个线程按顺序执行。
  • 所有的外界任务统一到一个线程中,所以不需要处理线程同步的问题。

     4、ThreadPoolExecutor介绍

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

 参数的介绍:   

  • corePoolSize:线程池的核心线程数,默认情况下核心线程会一直存活于线程池,即使处于闲置状态。
  • maximumPoolSize:线程池所能容纳的最大线程数,当线程数达到这个数值后,后续的新任务将会被阻塞。
  • keepAliveTime:非核心线程超时时长,超过这个时间没有任务执行,非核心线程就会被回收。
  • unit:用于指定keepAliveTime参数的时间单位。
  • workQueue:线程池中的任务队列。
  • threadFactory:线程工厂,为线程池提供创建新线程的功能。
  • handler:该handler的类型为RejectedExecutionHandler。当线程池无法执行新任务时,会调用handler的rejectedExecution(Runnable r, ThreadPoolExecutor e)方法来抛出异常。

   5、ThreadPoolExecutor执行任务大致遵循以下规则:

(1)如果线程池中的线程数量未达到核心线程的数量,会直接启动一个核心线程来执行任务。
(2)如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
(3)如果第2步中无法插入新任务,说明任务队列已满,如果未达到规定的最大线程数量,则启动一个非核心线程来执行任务。
(4)如果第3步中线程数量超过规定的最大值,则拒绝任务并使用RejectedExecutionHandler的rejectedExecution(Runnable r, ThreadPoolExecutor e)方法来通知调用者。

原文链接:https://www.jianshu.com/p/56163a3beb4a   

https://cloud.tencent.com/developer/article/1424838

                 

发布了49 篇原创文章 · 获赞 2 · 访问量 8592

猜你喜欢

转载自blog.csdn.net/yangjunjin/article/details/105025256