深入理解Android中的线程及线程间通信
来源:互联网 发布:雷神3知乎 编辑:程序博客网 时间:2024/05/22 17:15
Android应用启动时会创建哪些线程
从一个问题开始本文,当启动一个应用时,会创建哪些线程?UI线程是肯定有的,那么还有没有其他线程呢?
在Android Studio中通过adb shell命令可以查看应用的进程与线程信息,操作之前,先明确几个概念:
- UID——User ID,即用户id,在Android中,每个应用就代表一个用户,用户id在应用安装后就会分配。
- PID——Process identifier,即进程id。
- PPID——Parent process identifier,即父进程id,我们知道每个应用进程都派生自另一个进程,而Android中所有应用进程都派生自Zygote进程,也就是说Zygote进程是所有应用的父进程,待会儿我们也会证明这一事实。
针对个人的一个包名为me.geed.planner的应用,使用adb命令进行如下操作:
- 先查看Zygote的进程情况
- 再来看me.geed.planner应用的进程情况
- 最后打印me.geed.planner应用的线程信息
上述三步操作结果如下图所示:
其中第一列代表UID,第二列为PID,第三列为PPID,最后一列为Name。因此我们可以发现Zygote进程的PID为297。继续往下看,me.geed.planner应用的UID为u0_a228,其PID为22819,PPID为297,这就证明了Zygote是该应用的父进程。当然我这里只是打印了一个应用的信息,你也可以打印所有应用的进程信息,它们的父进程id都为297。再接着看图中应用的线程信息,第一个实际上是UI线程,可以发现后续几个线程都是UI线程的子线程,都是由UI线程派生出来的,由PPID都是22819可以证明。这里有一点需要理解即Linux中的进程与线程并没有严格意义上的区别,因此UI线程跟该应用的进程信息是一样的。
可以发现,应用启动时除了UI线程(UI线程在应用启动时创建并一直到进程结束),还创建了其他几个线程,如GC线程、Signal Catcher线程、Compiler线程及Binder线程等。Binder线程比较重要,是用于进程间通信的,Activity、Service等组件的启动均需要跟Binder线程打交道。
我们经常讨论到工作线程则是需要自己来创建启动的,而并非系统创建。
线程的调度
在Android中,应用的线程并不是由虚拟机来调度的,而是由Linux的标准调度器(Linux CFS)来调度的,因此线程不仅和自己应用中的线程竞争,也与其他app中的线程竞争。Linux CFS是指在内核2.6.23版本中推出的Completely Fair Scheduler,这款调度器不依赖于运行队列而是使用红黑树实现任务管理,更多资料可以参考这里。
Android中对线程的调度通常有优先级和线程组两种方式。
优先级
- Java线程优先级
java.lang.Thread.setPriority(int priority);
参数取值范围为[0,10],优先级从低到高,默认优先级为5。
- Linux线程优先级
android.os.Process.setThreadPriority(int priority)
android.os.Process.setThreadPriority(int threadId, int priority)
参数取值范围为[-20,19],优先级从高到低,默认优先级为0。
当然建议使用Android的线程优先级。
线程组
Android中主要的线程组有:Backgroud Group与Foreground Group。当app在前台运行,一般是在前台线程组,如果按下home键,app被切换到后台,则相应线程被切换到后台线程组。
应用中创建的线程默认跟UI线程在同一线程组并具有相同的优先级,UI线程的优先级为0。虽然UI线程更重要,但调度器不知道哪个是UI线程,哪个是我们创建的工作线程,因此UI线程在调度上并没有任何优势。所以,我们应该适当降低工作线程的优先级,使其归入到Backgroud Group。可利用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
将线程归入后台线程组,以提高UI线程的性能。
利用adb命令查看线程组情况,如下图:
可以发现UI线程为fg,即表示前台线程组。
线程间的通信
Pipes
Pipes位于java.io
包中,是java中的概念,它允许在同一进程中的两个线程之间进行单向通信。该机制遵循生产-消费模型。假设两个Thread分别为Producer线程和Consumer线程,则Producer负责写入数据,而Consumer线程负责读取数据,对应关系如下图:
pipe实质上是内存中分配的一块Buffer,只有通信的两个线程可以访问,其他线程不可以访问,并且利用pipe通信是单向的,因此能够保证线程安全。此Buffer的默认大小为1024kb,也可以自己配置。
利用Piles进行线程通讯只能传递二进制数据或字符数据。对于二进制数据的传输,Producer线程端借助PipedOutputStream
、Consumer线程端借助PipedInputStream
来完成;对于字符数据,Producer线程端借助PipedWriter
、Consumer线程端借助PipedReader
实现。
管道通信方式的生命周期起于读或写任一方发起的连接操作,当连接关闭时则终止。
由于此种通信方式实现了生产-消费模型,因此会产生阻塞,当pipe已经写满数据,此时Producer线程发生阻塞,不可再写入数据;当pipe中没有任何数据时,Consumer线程则发生阻塞,直到pipe里面被写入内容后才可以继续读取。因此,此种通信方式最好不要用在UI线程中,否则ANR就会成为你的噩梦。
Shared Memory
共享内存的通信方式就比较容易理解了,应用中的多个线程可以访问相同的一块地址空间,如果一个线程在共享内存中写入了数据,那么该数据就可以被其他线程读取,如下图所示:
Signaling
信号通知的机制比较抽象一些,但信号通知机制比共享内存的性能更高,它的原理是当状态值发生变化时,让某个线程通知其他线程。
举例来说明一下,例如当线程A需要等待线程B到达某个状态时才继续执行,此时线程A调用wait()/wait(timeout)
或等价的await()/await(timeout)
,timeout参数即调用线程的等待时间。当线程B满足了需要的状态,则会调用notify()/notifyAll()
或者等价的signal()/singalAll()
。通过接收到B线程发出的信号,原来等待的A线程继续执行。
Blocking Queue
虽然信号机制可以满足大多数的线程通信需求,但这种方式容易出错,于是基于信号通知机制,Java抽象出一种更通用的单向通信机制,即利用阻塞队列来实现Producer线程与Consumer线程的通信。其原理如下图所示:
BlockingQueue作为两个线程之间的协调者,消费者线程按顺序读取阻塞队列中的数据。同样,当队列中数据已满时,生产者线程就不能写入数据了,当队列中没有数据时,消费者线程也不能读取数据。
Android Message mechanism
Android消息机制应该是Android开发中最常用的通信机制了,它利用Handler、Looper、MessageQueue等完美实现线程间的切换及数据的传递,在这里就不展开细说了,可以参考之前写的Android消息机制源码解析系列文章:
- Android消息机制源码解析(一)——消息的载体Message
- Android消息机制源码解析(二)——消息的执行者Handler
- Android消息机制源码解析(三)——消息循环器Looper
- Android消息机制源码解析(四)——消息队列MessageQueue
参考文献
《Efficient Android Threading》
- 深入理解Android中的线程及线程间通信
- 深入理解DM8168中的线程通信
- Android中的线程通信
- 深入理解Java中的线程
- Android线程间通信机制——深入理解 Looper、Handler、Message
- 深入理解Android异步线程
- 深入理解Android线程池
- 深入理解Android多线程、线程同步及AsyncTask机制
- Android中的线程间通信(三)
- Android 多线程及线程通信
- Android 多线程及线程通信
- Android 多线程及线程通信
- 浅谈Android中的线程的通信及Handle机制
- android线程间通信
- Android线程间通信
- Android线程间通信
- android 线程间通信
- Android线程间通信
- pandas入门
- 英语总结——反思自身
- Demo14:图片渐入
- POJ 1274 The Perfect Stall
- 第三天:表格、表单及HTML框架集
- 深入理解Android中的线程及线程间通信
- ubuntu下thrift的安装
- does not contain bitcode. You must rebuild it with bitcode enabled
- CentOS 6.5下编译安装TFS
- 银行那些事儿--银行会计
- 《python基础教程》第三章 使用字符串
- Android 之 zygote 与进程创建
- Vijos P1097合并果子
- Windows安装Python、pip、easy_install