Android线程管理(三)——Thread类的内部原理、休眠及唤醒
线程通信、ActivityThread及Thread类是理解Android线程管理的关键。
线程,作为CPU调度资源的基本单位,在Android等针对嵌入式设备的操作系统中,有着非常重要和基础的作用。本小节主要从以下三个方面进行分析:
三、Thread类的内部原理、休眠及唤醒
3.1 Thread类的内部原理
线程是CPU资源调度的基本单位,属于抽象范畴,Java通过Thread类完成线程管理。Thread类本质其实是“可执行代码”,其实现了Runnable接口,而Runnable接口唯一的方法就是run()。
public class Thread implements Runnable {
……
}
public interface Runnable {
public void run();
}
从注释可以看出,调用Thread的start()方法就是间接调用Runnable接口的run()方法。
public synchronized void start() {
checkNotStarted();
hasBeenStarted = true;
VMThread.create(this, stackSize);
}
start()方法中VMThread.create(this, stackSize)是真正创建CPU线程的地方,换句话说,只有调用start()后的Thread才真正创建CPU线程,而新创建的线程中运行的就是Runnable接口的run()方法。
3.2 线程休眠及唤醒
线程通信、同步、协作是多线程编程中常见的问题。线程协作通常是采用线程休眠及唤醒来实现的,线程的休眠通过等待某个对象的锁(monitor)实现(wait()方法),当其他线程调用该对象的notify()方法时,该线程就被唤醒。该对象实现在线程间数据传递,多个线程通过该对象实现协作。
线程协作的经典例子是Java设计模式中的“生产者-消费者模式”,生产者不断往缓冲区写入数据,消费者从缓冲区中取出数据进行消费。在实现上,生产者与消费者分别继承Thread,缓冲区采用优先级队列PriorityQueue来模拟。生产者将数据放入缓冲区的前提是缓冲区有剩余空间,消费者从缓冲区中取出数据的前提是缓冲区中有数据,因此,这就涉及到生成者线程与消费者线程之间的协作。下面通过代码简要说明下。
import java.util.PriorityQueue;
public class TestWait {
private int size = 5;
private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(size);
public static void main(String[] args) {
TestWait test = new TestWait();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread {
@Override
public void run() {
while (true) {
synchronized (queue) {
while (queue.size() == 0) {
try {
System.out.println("队列空,等待数据");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notify();
}
}
queue.poll(); // 每次移走队首元素
queue.notify();
System.out.println("从队列取走一个元素,队列剩余" + queue.size() + "个元素");
}
}
}
}
class Producer extends Thread {
@Override
public void run() {
while (true) {
synchronized (queue) {
while (queue.size() == size) {
try {
System.out.println("队列满,等待有空余空间");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notify();
}
}
queue.offer(1); // 每次插入一个元素
queue.notify();
System.out.println("向队列取中插入一个元素,队列剩余空间:"
+ (size - queue.size()));
}
}
}
}
}
这段代码在很多讲述生产者-消费者模式的地方都会用到,其中,Producer线程首先启动,synchronized关键字使其能够获得queue的锁,其他线程处于等待状态。初始queue为空,通过offer向缓冲区队列写入数据,notify()方法使得等待该缓冲区queue的线程(此处为消费者线程)唤醒,但该线程并不能马上获得queue的锁,只有等生产者线程不断向queue中写入数据直到queue.size() == size,此时缓冲队列充满,生产者线程调用wait()方法进入等待状态。此时,消费者线程处于唤醒并且获得queue的锁,通过poll()方法消费缓冲区中的数据,同理,虽然调用了notify()方法使得生产者线程被唤醒,但其并不能马上获得queue的锁,只有等消费者线程不断消费数据直到queue.size() == 0,消费者线程调用wait()方法进入等待状态,生产者线程重新获得queue的锁,循环上述过程,从而完成生产者线程与消费者线程的协作。
在Android的SystemServer中有多处用到了线程协作的方式,比如WindowManagerService的main()中通过runWithScissors()启动的BlockingRunnable与SystemServer所在线程的协作。WindowManagerService源码地址可参考:https://github.com/android/platform_frameworks_base/blob/master/services/core/java/com/android/server/wm/WindowManagerService.java
3.3 线程中断
在Java中“中断”线程是通过interrupt()方法来实现的,之所以加引号,是因为interrupt()并不中断正在运行的线程,只是向线程发送一个中断请求,具体行为依赖于线程的状态,在文档中有如下说明:
Posts an interrupt request to this Thread. The behavior depends on the state of this Thread:
- Threads blocked in one of Object's wait() methods or one of Thread's join() or sleep() methods will be woken up, their interrupt status will be cleared, and they receive an InterruptedException.
- Threads blocked in an I/O operation of an java.nio.channels.InterruptibleChannel will have their interrupt status set and receive an java.nio.channels.ClosedByInterruptException. Also, the channel will be closed.
- Threads blocked in a java.nio.channels.Selector will have their interrupt status set and return immediately. They don't receive an exception in this case.
翻译下:
- 如果线程处于阻塞状态,即线程被Object.wait()、Thread.join()或 Thread.sleep()阻塞,调用interrupt()方法,将接收到InterruptedException异常,中断状态被清除,结束阻塞状态;
- 如果线程在进行I/O操作(java.nio.channels.InterruptibleChannel)时被阻塞,那么线程将收到java.nio.channels.ClosedByInterruptException异常,通道被关闭,结束阻塞状态;
- 如果线程被阻塞在java.nio.channels.Selector中,那么中断状态会被置位并返回,不会抛出异常。
public void interrupt() {
// Interrupt this thread before running actions so that other
// threads that observe the interrupt as a result of an action
// will see that this thread is in the interrupted state.
VMThread vmt = this.vmThread;
if (vmt != null) {
vmt.interrupt();
}
synchronized (interruptActions) {
for (int i = interruptActions.size() - 1; i >= 0; i--) {
interruptActions.get(i).run();
}
}
}
3.4 join()和sleep()方法
join()方法也可以理解为线程之间协作的一种方式,当两个线程需要顺序执行时,调用第一个线程的join()方法能使该线程阻塞,其依然通过wait()方法来实现的。
/**
* Blocks the current Thread (<code>Thread.currentThread()</code>) until
* the receiver finishes its execution and dies.
*
* @throws InterruptedException if <code>interrupt()</code> was called for
* the receiver while it was in the <code>join()</code> call
* @see Object#notifyAll
* @see java.lang.ThreadDeath
*/
public final void join() throws InterruptedException {
VMThread t = vmThread;
if (t == null) {
return;
}
synchronized (t) {
while (isAlive()) {
t.wait();
}
}
}
另外,还有带时间参数的join()方法,在超出规定时间后,退出阻塞状态。同样的,其通过带时间参数的wait()方法实现而已。
public final void join(long millis) throws InterruptedException{}
public final void join(long millis, int nanos) throws InterruptedException {}
sleep()与wait()的相同之处在于它们都是通过等待阻塞线程,不同之处在于sleep()等待的是时间,wait()等待的是对象的锁。
public static void sleep(long time) throws InterruptedException {
Thread.sleep(time, 0);
}
public static void sleep(long millis, int nanos) throws InterruptedException {
VMThread.sleep(millis, nanos);
}
3.5 CountDownLatch
CountDownLatch位于java.util.concurrent.CountDownLatch,实现倒数计数锁存器,当计数减至0时,触发特定的事件。在某些主线程需要等到子线程的应用很实用,以Google的zxing开源库中的一段代码为例进行说明:
final class DecodeThread extends Thread {
……
private final CountDownLatch handlerInitLatch;
DecodeThread(CaptureActivity activity,
Collection<BarcodeFormat> decodeFormats,
Map<DecodeHintType,?> baseHints,
String characterSet,
ResultPointCallback resultPointCallback) {
this.activity = activity;
handlerInitLatch = new CountDownLatch(1);
……
}
Handler getHandler() {
try {
handlerInitLatch.await();
} catch (InterruptedException ie) {
// continue?
}
return handler;
}
@Override
public void run() {
Looper.prepare();
handler = new DecodeHandler(activity, hints);
handlerInitLatch.countDown();
Looper.loop();
}
}
在上述例子中,首先在DecodeThread构造器中初始化CountDownLatch对象,并传入初始化参数1。其次,在run()方法中调用CountDownLatch对象的countDown()方法,这很好的保证了外部实例通过getHandler()方法获取handler时,handler不为null。
Android线程管理(三)——Thread类的内部原理、休眠及唤醒的更多相关文章
- Android线程管理(三)——Thread类的内部原理、休眠及唤醒
线程通信.ActivityThread及Thread类是理解Android线程管理的关键. 线程,作为CPU调度资源的基本单位,在Android等针对嵌入式设备的操作系统中,有着非常重要和基础的作用. ...
- Android线程管理之Thread使用总结
前言 最近在一直准备总结一下Android上的线程管理,今天先来总结一下Thread使用. 线程管理相关文章地址: Android线程管理之Thread使用总结 Android线程管理之Executo ...
- Android线程管理(二)——ActivityThread
线程通信.ActivityThread及Thread类是理解Android线程管理的关键. 线程,作为CPU调度资源的基本单位,在Android等针对嵌入式设备的操作系统中,有着非常重要和基础的作用. ...
- Android线程管理之ThreadPoolExecutor自定义线程池
前言: 上篇主要介绍了使用线程池的好处以及ExecutorService接口,然后学习了通过Executors工厂类生成满足不同需求的简单线程池,但是有时候我们需要相对复杂的线程池的时候就需要我们自己 ...
- Android线程管理之AsyncTask异步任务
前言: 前面几篇文章主要学习了线程以及线程池的创建与使用,今天来学习一下AsyncTask异步任务,学习下AsyncTask到底解决了什么问题?然而它有什么弊端?正所谓知己知彼百战百胜嘛! 线程管理相 ...
- Android线程管理之ThreadLocal理解及应用场景
前言: 最近在学习总结Android的动画效果,当学到Android属性动画的时候大致看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣 ...
- Android线程管理之ExecutorService线程池
前言: 上篇学习了线程Thread的使用,今天来学习一下线程池ExecutorService. 线程管理相关文章地址: Android线程管理之Thread使用总结 Android线程管理之Execu ...
- Android线程管理(一)——线程通信
线程通信.ActivityThread及Thread类是理解Android线程管理的关键. 线程,作为CPU调度资源的基本单位,在Android等针对嵌入式设备的操作系统中,有着非常重要和基础的作用. ...
- 线程中断:Thread类中interrupt()、interrupted()和 isInterrupted()方法详解
首先看看官方说明: interrupt()方法 其作用是中断此线程(此线程不一定是当前线程,而是指调用该方法的Thread实例所代表的线程),但实际上只是给线程设置一个中断标志,线程仍会继续运行. i ...
随机推荐
- Ajax请求与浏览器缓存
在现代Web应用程序中,前端代码充斥着大量的Ajax请求,如果对于Ajax请求可以使用浏览器缓存,那么可以显著地减少网络请求,提高程序响应速度. 1. Ajax Request 使用jQuery框架可 ...
- WEB安全 asp+access注入
asp+access注入 数据库 (access数据库没有数据库名) 表名 字段(列名) 记录(行,内容) 注入常用函数: top n 表示查询结果的第n个记录 len() 函数返回文本字段中值的长度 ...
- 【题解】洛谷P1966 [NOIP2013TG] 火柴排队(树状数组+逆序对)
次元传送门:洛谷P1966 思路 显然在两排中 每排第i小的分别对应就可取得最小值(对此不给予证明懒) 所以我们只在意两排的火柴是第几根 高度只需要用来进行排序(先把两个序列改成有序的方便离散化) 因 ...
- UVA - 1197 (简单并查集计数)
Severe acute respiratory syndrome (SARS), an atypical pneumonia of unknown aetiology, was recognized ...
- 支付宝在ios应用上的开发[转]
前奏 现在随着移动开发的快速发展,越来越多的应用要求在线支付功能.最近做了一个关于支付宝支付功能的应用,在使用支付宝的过程中,遇到一些不必要的弯路,因此,写了这篇文章总结一下关于ios开发如何使用支付 ...
- debug?用对拍!
很多人考noip之类的比赛永远会发生一些奇怪的问题 比如说下面这两位(来自我的两位学长) sliver n:spli,考得怎样啊? spli:就那样啦,day1T1没推出来规律,别的还好 silver ...
- 用JavaScript获取URL参数的方法之一
若地址栏URL为:abc.html?m=tomms&c=allsearchlist&pageNo=1&pageNum=20&text=1 <script> ...
- chrome浏览器页面获取绑定返回顶部动画事件插件
在chrome浏览器下页面加载: var top = $("body").scrollTop() ; console.log(top) ...
- jqu
1 /*2 * 说明:3 * 本源代码的中文注释乃Auscarlin呕心沥血所作.旨在促进jQuery的传播以及向广大jQuery爱好者提供一个进阶4 *的途径,以让各位更加深入地了解jQuery,学 ...
- spark+phoenix
phoenix作为查询引擎,为了提高查询效率,为phoenix表创建了二级索引,而数据是sparkstreaming通过hbase api直接向hbase插数据.那么问题来了,对于phoenix的二级 ...