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

上文對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()方法)抖坪,當(dāng)其他線程調(diào)用該對象的notify()方法時,該線程就被喚醒闷叉。該對象實現(xiàn)在線程間數(shù)據(jù)傳遞擦俐,多個線程通過該對象實現(xiàn)協(xié)作。

線程協(xié)作的經(jīng)典例子是Java設(shè)計模式中的“生產(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)時被阻塞寥假,那么線程將收到j(luò)ava.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é)作的一種方式,當(dāng)兩個線程需要順序執(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ù)鎖存器厚者,當(dāng)計數(shù)減至0時躁劣,觸發(fā)特定的事件。在某些主線程需要等到子線程的應(yīng)用很實用库菲,以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败去。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市烈拒,隨后出現(xiàn)的幾起案子圆裕,更是在濱河造成了極大的恐慌,老刑警劉巖荆几,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吓妆,死亡現(xiàn)場離奇詭異,居然都是意外死亡吨铸,警方通過查閱死者的電腦和手機行拢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诞吱,“玉大人舟奠,你說我怎么就攤上這事》课” “怎么了沼瘫?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咙俩。 經(jīng)常有香客問我耿戚,道長,這世上最難降的妖魔是什么阿趁? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任膜蛔,我火速辦了婚禮,結(jié)果婚禮上脖阵,老公的妹妹穿的比我還像新娘皂股。我一直安慰自己,他們只是感情好独撇,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布屑墨。 她就那樣靜靜地躺著躁锁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪卵史。 梳的紋絲不亂的頭發(fā)上战转,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音以躯,去河邊找鬼槐秧。 笑死,一個胖子當(dāng)著我的面吹牛忧设,可吹牛的內(nèi)容都是我干的刁标。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼址晕,長吁一口氣:“原來是場噩夢啊……” “哼膀懈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谨垃,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤启搂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后刘陶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胳赌,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年匙隔,在試婚紗的時候發(fā)現(xiàn)自己被綠了疑苫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡纷责,死狀恐怖捍掺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碰逸,我是刑警寧澤乡小,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布阔加,位于F島的核電站饵史,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏胜榔。R本人自食惡果不足惜胳喷,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望夭织。 院中可真熱鬧吭露,春花似錦、人聲如沸尊惰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至题禀,卻和暖如春鞋诗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背迈嘹。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工削彬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秀仲。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓融痛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親神僵。 傳聞我的和親對象是個殘疾皇子雁刷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容