成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

驚天秘密!從Thread開始,揭露Android線程通訊的詭計和主線程的陰謀

移動開發 Android
我們在Android開發過程中,幾乎都離不開線程。但是你對線程的了解有多少呢?它完美運行的背后,究竟隱藏了多少不為人知的秘密呢?線程間互通暗語,傳遞信息究竟是如何做到的呢?Looper、Handler、MessageQueue究竟在這背后進行了怎樣的運作。本期,讓我們一起從Thread開始,逐步探尋這個完美的線程鏈背后的秘密。

背景介紹

我們在Android開發過程中,幾乎都離不開線程。但是你對線程的了解有多少呢?它***運行的背后,究竟隱藏了多少不為人知的秘密呢?線程間互通暗語,傳遞信息究竟是如何做到的呢?Looper、Handler、MessageQueue究竟在這背后進行了怎樣的運作。本期,讓我們一起從Thread開始,逐步探尋這個***的線程鏈背后的秘密。

注意,大部分分析在代碼中,所以請仔細關注代碼哦!

從Tread的創建流程開始

在這一個環節,我們將一起一步步的分析Thread的創建流程。

話不多說,直接代碼里看。

線程創建的起始點init()

  1. // 創建Thread的公有構造函數,都調用的都是這個私有的init()方法。我們看看到底干什么了。 
  2.  
  3. /** 
  4.  
  5.      * 
  6.  
  7.      * @param 線程組 
  8.  
  9.      * @param 就是我們平時接觸最多的Runnable同學 
  10.  
  11.      * @param 指定線程的名稱 
  12.  
  13.      * @param 指定線程堆棧的大小 
  14.  
  15.      */ 
  16.  
  17. private void init(ThreadGroup g, Runnable target, String name, long stackSize) { 
  18.  
  19.         Thread parent = currentThread();            //先獲取當前運行中的線程。這一個Native函數,暫時不用理會它怎么做到的。黑盒思想,哈哈! 
  20.  
  21.         if (g == null) { 
  22.  
  23.             g = parent.getThreadGroup();            //如果沒有指定ThreadGroup,將獲取父線程的TreadGroup 
  24.  
  25.         } 
  26.  
  27.         g.addUnstarted();                           //將ThreadGroup中的就緒線程計數器增加一。注意,此時線程還并沒有被真正加入到ThreadGroup中。 
  28.  
  29.         this.group = g;                             //將Thread實例的group賦值。從這里開始線程就擁有ThreadGroup了。 
  30.  
  31.         this.target = target;                       //給Thread實例設置Runnable。以后start()的時候執行的就是它了。 
  32.  
  33.         this.priority = parent.getPriority();       //設置線程的優先權重為父線程的權重 
  34.  
  35.         this.daemon = parent.isDaemon();            //根據父線程是否是守護線程來確定Thread實例是否是守護線程。 
  36.  
  37.         setName(name);                              //設置線程的名稱   
  38.  
  39.         init2(parent);                              //納尼?又一個初始化,參數還是父線程。不急,稍后在看。 
  40.  
  41.         /* Stash the specified stack size in case the VM cares */ 
  42.  
  43.         this.stackSize = stackSize;                 //設置線程的堆棧大小 
  44.  
  45.         tid = nextThreadID();                       //線程的id。這是個靜態變量,調用這個方法會自增,然后作為線程的id。 
  46.  
  47.     }  

第二個init2() 

 

 

 

至此,我們的Thread就初始化完成了,Thread的幾個重要成員變量都賦值了。

啟動線程,開車啦!

通常,我們這樣了啟動一條線程。

  1. Thread threadDemo = new Thread(() -> { 
  2.  
  3.     }); 
  4.  
  5. threadDemo.start();  

那么start()背后究竟隱藏著什么樣不可告人的秘密呢?是人性的扭曲?還是道德的淪喪?讓我們一起點進start()。探尋start()背后的秘密。

  1. //如我們所見,這個方法是加了鎖的。原因是避免開發者在其它線程調用同一個Thread實例的這個方法,從而盡量避免拋出異常。 
  2.  
  3. //這個方法之所以能夠執行我們傳入的Runnable里的run()方法,是應為JVM調用了Thread實例的run()方法。 
  4.  
  5. public synchronized void start() { 
  6.  
  7.         //檢查線程狀態是否為0,為0表示是一個新狀態,即還沒被start()過。不為0就拋出異常。 
  8.  
  9.         //就是說,我們一個Thread實例,我們只能調用一次start()方法。 
  10.  
  11.         if (threadStatus != 0) 
  12.  
  13.             throw new IllegalThreadStateException(); 
  14.  
  15.         //從這里開始才真正的線程加入到ThreadGroup組里。再重復一次,前面只是把nUnstartedThreads這個計數器進行了增量,并沒有添加線程。 
  16.  
  17.         //同時,當線程啟動了之后,nUnstartedThreads計數器會-1。因為就緒狀態的線程少了一條啊! 
  18.  
  19.         group.add(this); 
  20.  
  21.         started = false
  22.  
  23.         try { 
  24.  
  25.             nativeCreate(this, stackSize, daemon);  //又是個Native方法。這里交由JVM處理,會調用Thread實例的run()方法。 
  26.  
  27.             started = true
  28.  
  29.         } finally { 
  30.  
  31.             try { 
  32.  
  33.                 if (!started) { 
  34.  
  35.                     group.threadStartFailed(this);  //如果沒有被啟動成功,Thread將會被移除ThreadGroup,同時,nUnstartedThreads計數器又增量1了。 
  36.  
  37.                 } 
  38.  
  39.             } catch (Throwable ignore) { 
  40.  
  41.                 
  42.  
  43.             } 
  44.  
  45.         } 
  46.  
  47.     }  

好吧,最精華的函數是native的,先當黑盒處理吧。只要知道它能夠調用到Thread實例的run()方法就行了。那我們再看看run()方法到底干了什么神奇的事呢? 

 

 

 

黑實驗 

 

 

 

上面的實驗表明了,我們完全可以用Thread來作為Runnable。

幾個常見的線程手段(操作)

Thread.sleep()那不可告人的秘密

我們平時使用Thread.sleep()的頻率也比較高,所以我們在一起研究研究Thread.sleep()被調用的時候發生了什么。

在開始之前,先介紹一個概念——納秒。1納秒=十億分之一秒。可見用它計時將會非常的精準。但是由于設備限制,這個值有時候并不是那么準確,但還是比毫秒的控制粒度小很多。

  1. //平時我們調用的Thread.sleep(long)***調用到這個方法來,后一個陌生一點的參數就是納秒。 
  2.  
  3. //你可以在納秒級控制線程。 
  4.  
  5. public static void sleep(long millis, int nanos) 
  6.  
  7.     throws InterruptedException { 
  8.  
  9.         //下面三個檢測毫秒和納秒的設置是否合法。 
  10.  
  11.         if (millis < 0) { 
  12.  
  13.             throw new IllegalArgumentException("millis < 0: " + millis); 
  14.  
  15.         } 
  16.  
  17.         if (nanos < 0) { 
  18.  
  19.             throw new IllegalArgumentException("nanos < 0: " + nanos); 
  20.  
  21.         } 
  22.  
  23.         if (nanos > 999999) { 
  24.  
  25.             throw new IllegalArgumentException("nanos > 999999: " + nanos); 
  26.  
  27.         } 
  28.  
  29.      
  30.  
  31.         if (millis == 0 && nanos == 0) { 
  32.  
  33.             if (Thread.interrupted()) {   //當睡眠時間為0時,檢測線程是否中斷,并清除線程的中斷狀態標記。這是個Native的方法。 
  34.  
  35.               throw new InterruptedException();  //如果線程被設置了中斷狀態為true了(調用Thread.interrupt())。那么他將拋出異常。如果在catch住這個異常之后return線程,那么線程就停止了。   
  36.  
  37.               //需要注意,在調用了Thread.sleep()之后,再調用isInterrupted()得到的結果永遠是False。別忘了Thread.interrupted()在檢測的同時還會清除標記位置哦! 
  38.  
  39.             } 
  40.  
  41.             return
  42.  
  43.         } 
  44.  
  45.         long start = System.nanoTime();  //類似System.currentTimeMillis()。但是獲取的是納秒,可能不準。 
  46.  
  47.         long duration = (millis * NANOS_PER_MILLI) + nanos;   
  48.  
  49.         Object lock = currentThread().lock;  //獲得當前線程的鎖。 
  50.  
  51.         synchronized (lock) {   //對當前線程的鎖對象進行同步操作 
  52.  
  53.             while (true) { 
  54.  
  55.                 sleep(lock, millis, nanos);  //這里又是一個Native的方法,并且也會拋出InterruptedException異常。 
  56.  
  57.                 //據我估計,調用這個函數睡眠的時長是不確定的。 
  58.  
  59.                 long now = System.nanoTime(); 
  60.  
  61.                 long elapsed = now - start;  //計算線程睡了多久了 
  62.  
  63.                 if (elapsed >= duration) {   //如果當前睡眠時長,已經滿足我們的需求,就退出循環,睡眠結束。 
  64.  
  65.                     break; 
  66.  
  67.                 } 
  68.  
  69.                 duration -= elapsed;   //減去已經睡眠的時間,重新計算需要睡眠的時長。 
  70.  
  71.                 start = now; 
  72.  
  73.                 millis = duration / NANOS_PER_MILLI;  //重新計算毫秒部分 
  74.  
  75.                 nanos = (int) (duration % NANOS_PER_MILLI); //重新計算微秒部分 
  76.  
  77.             } 
  78.  
  79.         } 
  80.  
  81.     }  

通過上面的分析可以知道,使線程休眠的核心方法就是一個Native函數sleep(lock, millis, nanos),并且它休眠的時常是不確定的。因此,Thread.sleep()方法使用了一個循環,每次檢查休眠時長是否滿足需求。

同時,需要注意一點,如果線程的interruted狀態在調用sleep()方法時被設置為true,那么在開始休眠循環前會拋出InterruptedException異常。

Thread.yield()究竟隱藏了什么?

這個方法是Native的。調用這個方法可以提示cpu,當前線程將放棄目前cpu的使用權,和其它線程重新一起爭奪新的cpu使用權限。當前線程可能再次獲得執行,也可能沒獲得。就醬。

無處不在的wait()究竟是什么?

大家一定經常見到,不論是哪一個對象的實例,都會在最下面出現幾個名為wait()的方法。等待?它們究竟是怎樣的一種存在,讓我們一起點擊去看看。

哎喲我去,都是Native函數啊。 

 

 

[[186444]] 

那就看看文檔它到底是什么吧。

根據文檔的描述,wait()配合notify()和notifyAll()能夠實現線程間通訊,即同步。在線程中調用wait()必須在同步代碼塊中調用,否則會拋出IllegalMonitorStateException異常。因為wait()函數需要釋放相應對象的鎖。當線程執行到wait()時,對象會把當前線程放入自己的線程池中,并且釋放鎖,然后阻塞在這個地方。直到該對象調用了notify()或者notifyAll()后,該線程才能重新獲得,或者有可能獲得對象的鎖,然后繼續執行后面的語句。

呃。。。好吧,在說明一下notify()和notifyAll()的區別。

  • notify()

調用notify()后,對象會從自己的線程池中(也就是對該對象調用了wait()函數的線程)隨機挑選一條線程去喚醒它。也就是一次只能喚醒一條線程。如果在多線程情況下,只調用一次notify(),那么只有一條線程能被喚醒,其它線程會一直在

  • notifyAll()

調用notifyAll()后,對象會喚醒自己的線程池中的所有線程,然后這些線程就會一起搶奪對象的鎖。

扒一扒Looper、Handler、MessageQueue之間的愛恨情仇

我們可能過去都寫過形如這樣的代碼: 

 

 

 

很多同學知道,在線程中使用Handler時(除了Android主線程)必須把它放在Looper.prepare()和Looper.loop()之間。否則會拋出RuntimeException異常。但是為什么要這么做呢?下面我們一起來扒一扒這其中的內幕。 

 

 

[[186445]] 

從Looper.prepare()開始

當Looper.prepare()被調用時,發生了什么? 

 

 

 

經過上面的分析,我們已經知道Looper.prepare()調用之后發生了什么。

但是問題來了!sThreadLocal是個靜態的ThreadLocal 實例(在Android中ThreadLocal的范型固定為Looper)。就是說,當前進程中的所有線程都共享這一個ThreadLocal。那么,Looper.prepare()既然是個靜態方法,Looper是如何確定現在應該和哪一個線程建立綁定關系的呢?我們接著往里扒。

來看看ThreadLocal的get()、set()方法。 

 

 

 

創建Handler

Handler可以用來實現線程間的通行。在Android中我們在子線程作完數據處理工作時,就常常需要通過Handler來通知主線程更新UI。平時我們都使用new Handler()來在一個線程中創建Handler實例,但是它是如何知道自己應該處理那個線程的任務呢。下面就一起扒一扒Handler。

  1. public Handler() { 
  2.  
  3.         this(nullfalse);  
  4.  
  5.  
  6.      
  7.  
  8. public Handler(Callback callback, boolean async) {      //可以看到,最終調用了這個方法。 
  9.  
  10.         if (FIND_POTENTIAL_LEAKS) { 
  11.  
  12.             final Class<? extends Handler> klass = getClass(); 
  13.  
  14.             if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && 
  15.  
  16.                     (klass.getModifiers() & Modifier.STATIC) == 0) { 
  17.  
  18.                 Log.w(TAG, "The following Handler class should be static or leaks might occur: " + 
  19.  
  20.                     klass.getCanonicalName()); 
  21.  
  22.             } 
  23.  
  24.         } 
  25.  
  26.         mLooper = Looper.myLooper();                    //重點啊!在這里Handler和當前Thread的Looper綁定了。Looper.myLooper()就是從ThreadLocale中取出當前線程的Looper。 
  27.  
  28.         if (mLooper == null) { 
  29.  
  30.             //如果子線程中new Handler()之前沒有調用Looper.prepare(),那么當前線程的Looper就還沒創建。就會拋出這個異常。 
  31.  
  32.             throw new RuntimeException( 
  33.  
  34.                 "Can't create handler inside thread that has not called Looper.prepare()"); 
  35.  
  36.         } 
  37.  
  38.         mQueue = mLooper.mQueue;  //賦值Looper的MessageQueue給Handler。 
  39.  
  40.         mCallback = callback; 
  41.  
  42.         mAsynchronous = async; 
  43.  
  44.     }  

Looper.loop()

我們都知道,在Handler創建之后,還需要調用一下Looper.loop(),不然發送消息到Handler沒有用!接下來,扒一扒Looper究竟有什么樣的魔力,能夠把消息準確的送到Handler中處理。

  1. public static void loop() { 
  2.  
  3.         final Looper me = myLooper();   //這個方法前面已經提到過了,就是獲取到當前線程中的Looper對象。 
  4.  
  5.         if (me == null) {  
  6.  
  7.             //沒有Looper.prepare()是要報錯的! 
  8.  
  9.             throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); 
  10.  
  11.         } 
  12.  
  13.         final MessageQueue queue = me.mQueue;       //獲取到Looper的MessageQueue成員變量,這是在Looper創建的時候new的。 
  14.  
  15.         //這是個Native方法,作用就是檢測一下當前線程是否屬于當前進程。并且會持續跟蹤其真實的身份。 
  16.  
  17.         //在IPC機制中,這個方法用來清除IPCThreadState的pid和uid信息。并且返回一個身份,便于使用restoreCallingIdentity()來恢復。 
  18.  
  19.         Binder.clearCallingIdentity(); 
  20.  
  21.         final long ident = Binder.clearCallingIdentity(); 
  22.  
  23.         for (;;) {  //重點(敲黑板)!這里是個死循環,一直等待抽取消息、發送消息。 
  24.  
  25.             Message msg = queue.next(); //  從MessageQueue中抽取一條消息。至于怎么取的,我們稍后再看。 
  26.  
  27.             if (msg == null) { 
  28.  
  29.                 // No message indicates that the message queue is quitting. 
  30.  
  31.                 return
  32.  
  33.             } 
  34.  
  35.             // This must be in a local variable, in case a UI event sets the logger 
  36.  
  37.             final Printer logging = me.mLogging; 
  38.  
  39.             if (logging != null) { 
  40.  
  41.                 logging.println(">>>>> Dispatching to " + msg.target + " " + 
  42.  
  43.                         msg.callback + ": " + msg.what); 
  44.  
  45.             } 
  46.  
  47.             final long traceTag = me.mTraceTag;   //取得MessageQueue的跟蹤標記 
  48.  
  49.             if (traceTag != 0) { 
  50.  
  51.                 Trace.traceBegin(traceTag, msg.target.getTraceName(msg));  //開始跟蹤本線程的MessageQueue中的當前消息,是Native的方法。 
  52.  
  53.             } 
  54.  
  55.             try { 
  56.  
  57.                 msg.target.dispatchMessage(msg);   //嘗試分派消息到和Message綁定的Handler中 
  58.  
  59.             } finally { 
  60.  
  61.                 if (traceTag != 0) { 
  62.  
  63.                     Trace.traceEnd(traceTag);      //這個和Trace.traceBegin()配套使用。 
  64.  
  65.                 } 
  66.  
  67.             } 
  68.  
  69.             if (logging != null) { 
  70.  
  71.                 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); 
  72.  
  73.             } 
  74.  
  75.              
  76.  
  77.             final long newIdent = Binder.clearCallingIdentity();   //what?又調用這個Native方法了。這里主要是為了再次驗證,線程所在的進程是否發生改變。 
  78.  
  79.             if (ident != newIdent) { 
  80.  
  81.                 Log.wtf(TAG, "Thread identity changed from 0x" 
  82.  
  83.                         + Long.toHexString(ident) + " to 0x" 
  84.  
  85.                         + Long.toHexString(newIdent) + " while dispatching to " 
  86.  
  87.                         + msg.target.getClass().getName() + " " 
  88.  
  89.                         + msg.callback + " what=" + msg.what); 
  90.  
  91.             } 
  92.  
  93.             msg.recycleUnchecked();   //回收釋放消息。 
  94.  
  95.         } 
  96.  
  97.     }  

從上面的分析可以知道,當調用了Looper.loop()之后,線程就就會被一個for(;;)死循環阻塞,每次等待MessageQueue的next()方法取出一條Message才開始往下繼續執行。然后通過Message獲取到相應的Handler (就是target成員變量),Handler再通過dispatchMessage()方法,把Message派發到handleMessage()中處理。

這里需要注意,當線程loop起來是時,線程就一直在循環中。就是說Looper.loop()后面的代碼就不能被執行了。想要執行,需要先退出loop。

  1. Looper myLooper = Looper.myLoop(); 
  2.  
  3. myLooper.quit();        //普通退出方式。 
  4.  
  5. myLooper.quitSafely();  //安全的退出方式。 

現在又產生一個疑問,MessageQueue的next()方法是如何阻塞住線程的呢?接下來,扒一扒這個幕后黑手MessageQueue。

幕后黑手MessageQueue

MessageQueue是一個用單鏈的數據結構來維護消息列表。 

 

 

 

可以看到。MessageQueue在取消息(調用next())時,會進入一個死循環,直到取出一條Message返回。這就是為什么Looper.loop()會在queue.next()處等待的原因。

那么,一條Message是如何添加到MessageQueue中呢?要弄明白***的真相,我們需要調查一下mHandler.post()這個方法。

Handler究竟對Message做了什么?

Handler的post()系列方法,最終調用的都是下面這個方法: 

 

 

 

接下來就看看MessageQueue的enqueueMessage()作了什么。 

 

 

 

至此,我們已經揭露了Looper、Handler、MessageQueue隱藏的秘密。

另一個疑問?

也許你已經注意到在主線程中可以直接使用Handler,而不需要Looper.prepare()和Looper.loop()。為什么可以做到這樣呢?根據之前的分析可以知道,主線程中必然存在Looper.prepare()和Looper.loop()。既然如此,為什么主線程沒有被loop()阻塞呢?看一下ActivityThread來弄清楚到底是怎么回事。 

 

 

 

注意ActivityThread并沒有繼承Thread,它的Handler是繼承Handler的私有內部類H.class。在H.class的handleMessage()中,它接受并執行主線程中的各種生命周期狀態消息。UI的16ms的繪制也是通過Handler來實現的。也就是說,主線程中的所有操作都是在Looper.prepareMainLooper()和Looper.loop()之間進行的。進一步說是在主Handler中進行的。

總結

  1. Android中Thread在創建時進行初始化,會使用當前線程作為父線程,并繼承它的一些配置。
  2. Thread初始化時會被添加到指定/父線程的ThreadGroup中進行管理。
  3. Thread正真啟動是一個native函數完成的。
  4. 在Android的線程間通信中,需要先創建Looper,就是調用Looper.prepare()。這個過程中會自動依賴當前Thread,并且創建MessageQueue。經過上一步,就可以創建Handler了,默認情況下,Handler會自動依賴當前線程的Looper,從而依賴相應的MessageQueue,也就知道該把消息放在哪個地方了。MessageQueue通過Message.next實現了一個單鏈表結構來緩存Message。消息需要送達Handler處理,還必須調用Looper.loop()啟動線程的消息泵送循環。loop()內部是***循環,阻塞在MessageQueue的next()方法上,因為next()方法內部也是一個***循環,直到成功從鏈表中抽取一條消息返回為止。然后,在loop()方法中繼續進行處理,主要就是把消息派送到目標Handler中。接著進入下一次循環,等待下一條消息。由于這個機制,線程就相當于阻塞在loop()這了。

經過上面的揭露,我們已經對線程及其相互之間通訊的秘密有所了解。掌握了這些以后,相信在以后的開發過程中我們可以思路清晰的進行線程的使用,并且能夠吸收Android在設計過程中的精華思想。

責任編輯:龐桂玉 來源: 安卓巴士Android開發者門戶
相關推薦

2012-07-26 15:15:03

微軟Office2013蘋果

2011-06-22 15:42:18

QT 信號

2015-03-16 14:02:35

GoogleAndroidCyanogen

2024-05-20 08:21:36

Activity內部類接口

2025-04-28 08:20:40

ndroid主線程線程

2010-03-15 18:34:08

Java多線程

2022-07-27 13:44:51

數據泄露網絡安全

2010-02-24 11:19:00

Python主線程

2025-05-12 04:00:01

2021-06-16 14:18:37

NettyReactor線程模型

2014-04-08 14:19:06

Android開發UI線程

2018-06-08 18:03:33

華為云

2020-09-21 06:43:59

AtomicIntegNumber內存

2009-06-29 17:54:10

Java多線程Thread類創建線程

2021-09-11 15:26:23

Java多線程線程池

2012-05-14 17:09:05

iOS

2020-12-21 06:18:15

Android線程主線程

2022-08-29 10:52:37

線程函數操作系統

2025-05-22 08:28:59

Android主線程Activity

2017-06-23 15:45:09

AndroidThread
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 九一在线 | 亚洲成人动漫在线观看 | 一区二区高清 | 日本久久一区二区三区 | 日韩在线视频网址 | 免费观看一级毛片 | 蜜桃特黄a∨片免费观看 | 四虎永久免费影院 | 久久精品国产免费 | 亚洲网在线 | 久久国产精品99久久久大便 | 91视频精选 | 在线视频中文字幕 | 欧美亚洲国产一区 | 精品亚洲一区二区 | 免费观看黄| 精品久久影院 | 久久久久久91香蕉国产 | 午夜二区| 亚洲区一区二 | 国产成人免费视频网站高清观看视频 | 色综合久 | 亚洲国产精品99久久久久久久久 | 久久久久久久夜 | 91豆花视频 | 亚洲精品电影在线观看 | 91久久久久久| 亚洲伊人a| 成人区精品一区二区婷婷 | 一区二区免费高清视频 | a免费视频 | 黄色免费在线网址 | 97国产精品 | 九九九视频精品 | 欧美日韩一区二区视频在线观看 | 日本一区二区三区视频在线 | 欧美日韩在线精品 | 最新中文字幕在线 | 国产成人99久久亚洲综合精品 | 成人在线精品 | 国产精品久久国产精品 |