線程通信、ActivityThread及Thread類是理解Android線程管理的關鍵。 線程,作為CPU調度資源的基本單位,在Android等針對嵌入式設備的操作系統中,有著非常重要和基礎的作用。本小節主要從以下三個方面進行分析: 《Android線程管理(一)——線程通信》 《Android線...
線程通信、ActivityThread及Thread類是理解Android線程管理的關鍵。
線程,作為CPU調度資源的基本單位,在Android等針對嵌入式設備的操作系統中,有著非常重要和基礎的作用。本小節主要從以下三個方面進行分析:
三、Thread類的內部原理、休眠及喚醒
3.1 Thread類的內部原理
線程是CPU資源調度的基本單位,屬於抽象範疇,Java通過Thread類完成線程管理。Thread類本質其實是“可執行代碼”,其實現了Runnable介面,而Runnable介面唯一的方法就是run()。
public class Thread implements Runnable { …… }
public interface Runnable { /** * Starts executing the active part of the class' code. This method is * called when a thread is started that has been created with a class which * implements {@code 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。