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 ...
随机推荐
- WP8 SqlCE和SqlLite数据存储性能比较
在平时的开发中一定会用到本地数据存储,除了独立存储外我们还可以选择SqlCE和SqlLite:于是在选择上我们就必须权衡他们两者的性能择优选择. 测试代码:(这个例子是在msdn sqllite例子上 ...
- 关于Assembly.CreateInstance()与Activator.CreateInstance()方法
于Assembly.CreateInstance()与Activator.CreateInstance()方法 动 态创建类对象,大多是Activator.CreateInstance()和Activ ...
- 用httpclient访问时,附带cookie信息
var url = "address"; var cookieContainer = new CookieContainer(); using (var handler = new ...
- 斜堆(三)之 Java的实现
概要 前面分别通过C和C++实现了斜堆,本章给出斜堆的Java版本.还是那句老话,三种实现的原理一样,择其一了解即可. 目录1. 斜堆的介绍2. 斜堆的基本操作3. 斜堆的Java实现(完整源码)4. ...
- Android SDK之API Level
Android SDK之API Level Android SDK API_LEVEL Platform Version API Level VERSION_CODE Android 4.2, 4.2 ...
- Spring MVC全局异常处理与拦截器校检
在使用Spring MVC进行开发时,总是要对系统异常和用户的异常行为进行处理,以提供给用户友好的提示,也可以提高系统的安全性. 拦截系统响应错误 首先是拦截系统响应错误,这个可以在web.xml中配 ...
- EPANET中读取INPUT文件的函数文件——INPUT1.C/INPUT2.C/INPUT3.C
首先介绍下这3个文件的关系:可以说INPUT1.C的函数粒度最大,它的函数getdata()就完成了整个INPUT文件数据的读入,该函数又调用了INPUT2.C中的部分函数,INPUT2.C文件中的函 ...
- .NET指定程序集的位置
有两种方法用来指定程序集的位置: 使用 <codeBase> 元素. 使用 <probing> 元素. 还可以使用 .NET Framework 配置工具 (Mscorcfg. ...
- statpot:使用mongo+bootstrap+highcharts做统计报表
最近做了一个统计项目,这个统计项目大致的需求是统计接口的访问速度.客户端会调用一个接口来记录接口的访问情况,我的需求就需要分析这些数据,然后做出个统计报表. 需求实现 最初的时候想着每天把这些接口访问 ...
- 关于 CommonJS AMD CMD UMD 规范的差异总结
一.CommonJS 主要是用于服务器端的规范,比如目前的nodeJS. 根据CommonJS规范,一个单独的文件就是一个模块.每一个模块都是一个单独的作用域,也就是说,在一个文件定义的变量(还包括函 ...