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 ...
随机推荐
- C#中子类与父类的相互转换
1.父类不能直接强制转换成子类 2.子类可以强制转换成父类,但是在父类中只能取父类的字段与方法 因此在一个父类对应多个子类的时候,不知道具体是哪个子类的时候,就可以先声明一个父类的类型.(如例1) 3 ...
- [ML] Naive Bayes for email classification
20 Newsgroups (Original) Author: Jeffrey H 1. Introduction This is only a test report for naive baye ...
- [编辑器]走上atom之路1
祝大家新年快乐 我就是来卖个萌,逃- 正文 我最开始用atom是因为它看起来比较酷,我工作中主力还是使用pycharm,毕竟atom只是一个编辑器.我一 般只是用atom来写Markdown的文件.随 ...
- UWP开发入门(十四)—— UserControl中Adaptive UI的小技巧
本篇我们通过绘制一个非常简单的UserControl控件,来分享一下对Adaptive UI的理解及一些图形绘制的技巧. 现在流行的APP都少不了精致的用户头像,首先假设我们需要绘制如下的图形作为默认 ...
- Node.js爬虫数据抓取 -- 问题总结
一 返回的信息提示 Something went wrong request模块请求出现未知错误 其中,所用代码如下(无User-Agent部分) 问题多次派查无果,包括: 1:postman请 ...
- .NET 配置文件简单使用
当我们开发系统的时候要把一部分设置提取到外部的时候,那么就要用到.NET的配置文件了.比如我的框架中使用哪个IOC容器需要可以灵活的选择,那我就需要把IOC容器的设置提取到配置文件中去配置.实现有几种 ...
- 以对象的方式来访问xml数据表(一)
所有实例代码都是以C#演示—— 在将如何以对象的方式来访问xml数据表之前,我们先来谈谈如何用xml文件作为数据库吧! 平时在开发一些小的应用的时候,需要一个数据库,这个时候虽然可以用SQL serv ...
- css:条件注释判断浏览器
所有的IE可识别 Target ALL VERSIONS of IE <!--[if IE]> <link rel="stylesheet" type=" ...
- ComponentOne Studio for Enterprise 2015 v1 全新发布
ComponentOne Studio 即将发布2015年的第一个版本.2015 v1版本聚焦于优化性能.提升数据可视化能力.加强数据管理以及更人性的输入方式及报表解决方案. 免费下载试用 WinFo ...
- kFreeBSD 7.0于2013/05/04发布 桌面环境 GNOME 3....
kFreeBSD 7.0于2013/05/04发布 桌面环境 GNOME 3.4, KDE 4.8.4, Xfce 4.8, and LXDE