Android線程管理(三)——Thread類的內部原理、休眠及喚醒

来源:http://www.cnblogs.com/younghao/archive/2016/01/27/5141295.html
-Advertisement-
Play Games

線程通信、ActivityThread及Thread類是理解Android線程管理的關鍵。 線程,作為CPU調度資源的基本單位,在Android等針對嵌入式設備的操作系統中,有著非常重要和基礎的作用。本小節主要從以下三個方面進行分析: 《Android線程管理(一)——線程通信》 《Android線...


      線程通信、ActivityThread及Thread類是理解Android線程管理的關鍵。

      線程,作為CPU調度資源的基本單位,在Android等針對嵌入式設備的操作系統中,有著非常重要和基礎的作用。本小節主要從以下三個方面進行分析:

  1. 《Android線程管理(一)——線程通信》
  2. Android線程管理(二)——ActivityThread》 
  3. Android線程管理(三)——Thread類的內部原理、休眠及喚醒


三、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。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 引用類型的值(對象)是引用類型的一個實例,在ES中引用類型是一種數據結構,將數據和功能組織在一起。引用類型有時候也被稱之為對象定義,因為他們描述的是一類對象所具有的屬性和方法。Object類型兩種創建方式1 new Object()var person = new Object();person.n...
  • 嚴格模式是一種將更好的錯誤檢查引入代碼中的方法。在使用嚴格模式時,無法使用隱式聲明的變數、將值賦給只讀屬性或將屬性添加到不可擴展的對象等1、嚴格模式的目的1)消除Javascript語法的一些不合理、不嚴謹之處,減少一些怪異行為 2)消除代碼運行的一些不安全之處,保證代碼運行的安全 3) 提...
  • 如果說Origami這款動效原型工具是Facebook Paper的幕後功臣,那麼POP便是Origami的地基。感謝Facebook開源了POP動效庫,讓人人都能製作出華麗的動效。我們只需5步,便能搞定酷炫的動效。步驟1: 安裝使用CocoaPods安裝POP,只需要在Podfile中加入這麼一行...
  • 在做一些安全性的軟體時候常常要考慮取消 EditText 上的複製粘貼功能以確保全全性。下麵就記錄了這個方法:首先在API-11以下的版本很簡單,只需要在Xml佈局文件或者用代碼把長按屬性設置成false就可以。1 editText.setLongClickable(false); // Xml l...
  • 在 App 開發中我們經常需要在用戶登錄模塊接入 SNS 登錄組件,這樣會大大提高用戶的註冊體驗。特別當一個不是剛性需求 App 推廣的時候,這樣會很大的降低用戶體驗的成本,沒有人願意忍受輸入郵箱、手機號碼去註冊一個賬號的流程。 本文主要分享了在 React Native 中接入微博、微信、QQ ...
  • 1、首先簽名是個什麼東西。 應用程式簽名就是為你的程式打上一種標記,來作為你自己的標識。2、為什麼要進行數字簽名 這是Android系統的要求,每一個應用程式必要要經過數字簽名才可能安裝到系統中,能安裝的apk則是已經簽名了的。 apk不簽名是安裝不了的,但是別人也可以重新簽名。 使用你自己...
  • 在一些公司的項目中,為了避免刷註冊量等,大多數人會採取繪製本地圖片驗證碼來減少這些操作。這樣註冊時,不僅需要判斷手機驗證碼,還要判斷一次本地的圖片驗證碼。首先展示下效果圖:點擊獲取手機驗證碼時或者填寫完基本信息點擊註冊按鈕時,都會判斷圖片驗證碼是否正確,不正確的話晃動驗證碼,改變圖片內容。其實圖案內...
  • iOS 枚舉是比較常用的結構. 枚舉的變數是從0開始的NSInteger. 可以看做是一個#define .列舉常用的定義方式: 1 @interface ViewController () 2 /** 3 這種比較推薦,結構清晰,使用時可以省略關鍵字:enum .蘋果官方推薦. 4 */ 5 .....
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...