Android 多线程

01.Android中的线程

  • 主线程(有的也成UI线程)
    • 在Android当中, 当应用启动的时候,系统会给应用分配一个进程,顺便一提,大部分应用都是单进程的,不过也可以通过设置来使不同组件运行在不同的进程中,在创建进程的同时会创建一个线程,应用的大部分操作都会在这个线程中运行。所以称为主线程,同时所有的UI控件相关的操作也要求在这个线程中操作,所以也称为UI线程。
  • 为何会有子线程
    • 因为所有的UI控件的操作都在UI线程中执行,如果在UI线程中执行耗时操作,例如网络请求等,就会阻塞UI线程,导致系统报ANR(Application Not Response)错误。因此对于耗时操作需要创建工作线程来执行而不能直接在UI线程中执行。这样就需要在应用中使用多线程,但是Android提供的UI工具包并不是线程安全的,也就是说不能直接在工作线程中访问UI控件,否则会导致不能预测的问题, 因此需要额外的机制来进行线程交互,主要是让其他线程可以访问UI线程。

02.线程交互之Handler机制

  • 在Android当中, 工作线程主要通过Handler机制来访问UI线程。当然还有一些封装好的类例如AsyncTask可以使用, 但是本质仍是使用Handler。
  • Handler机制主要由4部分组成, Looper, 消息队列, 消息类和Handler组成。
    • 其中Looper和消息队列是和线程绑定的, 每个线程只会有一个Looper和一个消息队列, 当Looper启动时,它会无限循环尝试从消息队列中获取消息实例,如果没有消息则会阻塞等待。当Handler发送消息时会把消息实例放入消息队列中,Looper从中取得消息实例然后就会调用Handler的相关方法,因为Looper是线程绑定的, 如果绑定的是UI线程,那么此时Handler的方法就会在UI线程中得到执行,线程间就是这样进行交互的。

03.线程切换的类

  • 线程切换的类
    • AsyncTask:底层封装了线程池和Handler,便于执行后台任务以及在子线程中进行UI操作。
    • HandlerThread:一种具有消息循环的线程,其内部可使用Handler。
    • IntentService:是一种异步、会自动停止的服务,内部采用HandlerThread。
    • Handler+Thread:这种很常见
  • AsyncTask处理多线程优缺点
    • AsyncTask是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程。
    • 1.处理单个异步任务简单,可以获取到异步任务的进度
    • 2.可以通过cancel方法取消还没执行完的AsyncTask
    • 3.处理多个异步任务代码显得较多
    • 使用范围:单个异步任务的处理
  • Handler+Thread处理多线程优缺点
    • Handler用法简单明了,可以将多个异步任务更新UI的代码放在一起,清晰明了
    • 处理单个异步任务代码略显多
    • 使用范围:多个异步任务的更新UI

04.java中的多线程

  • 而Handler机制的底层实现则是使用java多线程相关的类.
    • java当中主要使用Thread和Executor来实现多线程. Thread用于直接创建线程, 在Android中也可以直接使用这个类, Looper中就包含一个Thread实例. Executor是一个接口, 大部分java中自带的实现都使用了线程池来管理多线程。

05.线程池管理多线程

  • 因为在系统中创建线程是一个比较耗费资源的事, 所以不能频繁创建和释放线程, 因此在效率上考虑通常会使用线程池, 同时也便于线程的管理. Android中的AsyncTask就使用了线程池。

06.Handler和AsyncTask

  • Handler机制存在的问题
    • 多任务同时执行时不易精确控制线程
  • AsyncTask优势
    • 创建异步任务更简单,直接继承它可方便实现后台异步任务的执行和进度的回调更新UI,而无需编写任务线程和Handler实例就能完成相同的任务。

07.线程安全问题

  • 线程安全问题
    • 使用多线程时需要注意的是线程安全的问题, 因为同一进程中的线程可以共享内存, 虽然这种方式效率很高, 但是会导致线程干扰和内存一致性的问题。
  • 锁机制
    • 解决这些问题的主要方法是使用Synchronized关键字来加锁. 基本原理就是线程要对对象进行操作前需要先获取锁, 如果一个线程正在操作某个对象, 那么它就会持有相应的锁, 后来的线程想要操作这个对象, 只能等待前面的线程释放锁之后才有机会获取锁并进行操作.