上文對ActivityThread的工作流程進行了分析,本文將對Thread類的內(nèi)部原理以及休眠喚醒進行說明。
三、Thread類的內(nèi)部原理、休眠及喚醒
3.1 Thread類的內(nèi)部原理
線程是CPU資源調(diào)度的基本單位,屬于抽象范疇,Java通過Thread類完成線程管理。Thread類本質(zhì)其實是“可執(zhí)行代碼”,其實現(xiàn)了Runnable接口,而Runnable接口唯一的方法就是run()。
public class Thread implements Runnable {
……
}
public interface Runnable {
public void run();
}
從注釋可以看出,調(diào)用Thread的start()方法就是間接調(diào)用Runnable接口的run()方法。
public synchronized void start() {
checkNotStarted();
hasBeenStarted = true;
VMThread.create(this, stackSize);
}
start()方法中VMThread.create(this, stackSize)是真正創(chuàng)建CPU線程的地方,換句話說,只有調(diào)用start()后的Thread才真正創(chuàng)建CPU線程,而新創(chuàng)建的線程中運行的就是Runnable接口的run()方法。
3.2 線程休眠及喚醒
線程通信、同步、協(xié)作是多線程編程中常見的問題。線程協(xié)作通常是采用線程休眠及喚醒來實現(xiàn)的,線程的休眠通過等待某個對象的鎖(monitor)實現(xiàn)(wait()方法),當其他線程調(diào)用該對象的notify()方法時,該線程就被喚醒。該對象實現(xiàn)在線程間數(shù)據(jù)傳遞,多個線程通過該對象實現(xiàn)協(xié)作。
線程協(xié)作的經(jīng)典例子是Java設計模式中的“生產(chǎn)者-消費者模式”,生產(chǎn)者不斷往緩沖區(qū)寫入數(shù)據(jù),消費者從緩沖區(qū)中取出數(shù)據(jù)進行消費。在實現(xiàn)上,生產(chǎn)者與消費者分別繼承Thread,緩沖區(qū)采用優(yōu)先級隊列PriorityQueue來模擬。生產(chǎn)者將數(shù)據(jù)放入緩沖區(qū)的前提是緩沖區(qū)有剩余空間,消費者從緩沖區(qū)中取出數(shù)據(jù)的前提是緩沖區(qū)中有數(shù)據(jù),因此,這就涉及到生成者線程與消費者線程之間的協(xié)作。下面通過代碼簡要說明下。
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("隊列空,等待數(shù)據(jù)");
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()));
}
}
}
}
}
這段代碼在很多講述生產(chǎn)者-消費者模式的地方都會用到,其中,Producer線程首先啟動,synchronized關(guān)鍵字使其能夠獲得queue的鎖,其他線程處于等待狀態(tài)。初始queue為空,通過offer向緩沖區(qū)隊列寫入數(shù)據(jù),notify()方法使得等待該緩沖區(qū)queue的線程(此處為消費者線程)喚醒,但該線程并不能馬上獲得queue的鎖,只有等生產(chǎn)者線程不斷向queue中寫入數(shù)據(jù)直到queue.size() ==size,此時緩沖隊列充滿,生產(chǎn)者線程調(diào)用wait()方法進入等待狀態(tài)。此時,消費者線程處于喚醒并且獲得queue的鎖,通過poll()方法消費緩沖區(qū)中的數(shù)據(jù),同理,雖然調(diào)用了notify()方法使得生產(chǎn)者線程被喚醒,但其并不能馬上獲得queue的鎖,只有等消費者線程不斷消費數(shù)據(jù)直到queue.size() == 0,消費者線程調(diào)用wait()方法進入等待狀態(tài),生產(chǎn)者線程重新獲得queue的鎖,循環(huán)上述過程,從而完成生產(chǎn)者線程與消費者線程的協(xié)作。
在Android的SystemServer中有多處用到了線程協(xié)作的方式,比如WindowManagerService的main()中通過runWithScissors()啟動的BlockingRunnable與SystemServer所在線程的協(xié)作。WindowManagerService源碼地址可參考:WindowManagerService.java
3.3 線程中斷
在Java中“中斷”線程是通過interrupt()方法來實現(xiàn)的,之所以加引號,是因為interrupt()并不中斷正在運行的線程,只是向線程發(fā)送一個中斷請求,具體行為依賴于線程的狀態(tài),在文檔中有如下說明:
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.
翻譯下:
- 如果線程處于阻塞狀態(tài),即線程被Object.wait()、Thread.join()或 Thread.sleep()阻塞,調(diào)用interrupt()方法,將接收到InterruptedException異常,中斷狀態(tài)被清除,結(jié)束阻塞狀態(tài);
- 如果線程在進行I/O操作(java.nio.channels.InterruptibleChannel)時被阻塞,那么線程將收到java.nio.channels.ClosedByInterruptException異常,通道被關(guān)閉,結(jié)束阻塞狀態(tài);
- 如果線程被阻塞在java.nio.channels.Selector中,那么中斷狀態(tài)會被置位并返回,不會拋出異常。
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()方法也可以理解為線程之間協(xié)作的一種方式,當兩個線程需要順序執(zhí)行時,調(diào)用第一個線程的join()方法能使該線程阻塞,其依然通過wait()方法來實現(xiàn)的。
/**
* 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();
}
}
}
另外,還有帶時間參數(shù)的join()方法,在超出規(guī)定時間后,退出阻塞狀態(tài)。同樣的,其通過帶時間參數(shù)的wait()方法實現(xiàn)而已。
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,實現(xiàn)倒數(shù)計數(shù)鎖存器,當計數(shù)減至0時,觸發(fā)特定的事件。在某些主線程需要等到子線程的應用很實用,以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構(gòu)造器中初始化CountDownLatch對象,并傳入初始化參數(shù)1。其次,在run()方法中調(diào)用CountDownLatch對象的countDown()方法,這很好的保證了外部實例通過getHandler()方法獲取handler時,handler不為null。