重走JAVA之路(六):你應該要知道的線程調度

做爲Android開發者,老實說,日常關於一些線程調度的方法,用的確實很少,可能用的最多的也就是sleep做爲一個休眠延時的操做,可是既然是Java之路,那就必須把那些東西拎出來講一說了,也是增強你們對線程的理解程度以及在處理線程中應該注意的問題。java

1.join() 等待線程終止android

這個方法你們可能用的很少,咱們想象一個場景:主線程生成並起動了子線程,若是子線程裏要進行大量的耗時的運算,主線程每每將於子線程以前結束,可是若是主線程處理完其餘的事務後,須要用到子線程的處理結果,也就是主線程須要等待子線程執行完成以後再結束,這個時候咱們第一想法多是要否則在子線程中處理完以後,用Handler把消息傳到主線程再處理?這樣每每比較麻煩,這個時候就能夠用join方法來實現面試

public class TestClass {
    public static void main(String []agrs){
       Thread thread = new Thread(new Runnable() {
           @Override
           public void run() {
               System.out.println("child thread start");
               try {
                   //模擬耗時操做
                   Thread.sleep(3000);
               } catch (InterruptedException mE) {
                   mE.printStackTrace();
               }
               System.out.println("child thread over");
           }
       });
       thread.start();
        try {
            thread.join();
        } catch (InterruptedException mE) {
            mE.printStackTrace();
        }
        System.out.println("main thread call");
    }
}
複製代碼

運行,打印一波,能夠看到調用join方法以後,主線程就能夠在主線程執行完成以後,處理邏輯了bash

child thread start
child thread over
main thread call
Process finished with exit code 0
複製代碼

2.wait()/notifyAll-----經典的生產者消費者問題ide

話很少說,直接上代碼oop

public class Model {
    //爲了觸發阻塞狀態,這裏把最大容量設置爲1
    public static final int MAX_SIZE = 1;
    //存儲數據的集合
    public static LinkedList<Integer> list = new LinkedList<>();

    class Producer implements Runnable {
        @Override
        public void run() {
            synchronized (list) {
                //倉庫容量已經達到最大值
                while (list.size() == MAX_SIZE) {
                    System.out.println(Thread.currentThread().getName() + " no need to produce! repertory is full");
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                list.add(1);
                System.out.println( Thread.currentThread().getName() + " produce,current repertory is " + list.size());
                list.notifyAll();
            }
        }
    }

    class Consumer implements Runnable {

        @Override
        public void run() {
            synchronized (list) {
                while (list.size() == 0) {
                    System.out.println(Thread.currentThread().getName() + " no product to consume! repertory is empty ");
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                list.removeFirst();
                System.out.println(Thread.currentThread().getName() + " consume,current repertory is " + list.size());
                list.notifyAll();
            }
        }
    }

}
複製代碼

主要代碼就是調用wait/notifyAll方法,分別在極限狀況時,對線程進行掛起以及喚醒,消費者和生產者開啓10個線程來測試一波post

public class TestClass {
    public static void main(String []agrs){
       Model model = new Model();
       Model.Producer producer = model.new Producer();
       Model.Consumer consumer = model.new Consumer();
        for (int i = 0; i < 10; i++) {
            Thread proThread  = new Thread(producer);
            proThread.start();
            Thread conThread = new Thread(consumer);
            conThread.start();
        }
    }
}
複製代碼
Thread-0 produce,current repertory is 1
Thread-2 no need to produce! repertory is full
Thread-1 consume,current repertory is 0
Thread-2 produce,current repertory is 1
Thread-3 consume,current repertory is 0
Thread-5 no product to consume! repertory is empty 
Thread-4 produce,current repertory is 1
Thread-5 consume,current repertory is 0
Thread-7 no product to consume! repertory is empty 
Thread-6 produce,current repertory is 1
Thread-7 consume,current repertory is 0
Thread-8 produce,current repertory is 1
Thread-9 consume,current repertory is 0
Thread-10 produce,current repertory is 1
Thread-11 consume,current repertory is 0
Thread-12 produce,current repertory is 1
Thread-13 consume,current repertory is 0
Thread-14 produce,current repertory is 1
Thread-15 consume,current repertory is 0
Thread-16 produce,current repertory is 1
Thread-17 consume,current repertory is 0
Thread-18 produce,current repertory is 1
Thread-19 consume,current repertory is 0

Process finished with exit code 0
複製代碼

能夠看到,當Thread-2要去生產時,發現此時倉庫以及滿了,此時調用wait方法,釋放鎖,同時線程阻塞,注意,這裏線程並不會結束掉,只是出於掛起狀態,當下次被喚醒時,會沿着wait方法後面繼續執行,在第四行也能夠看到,當Thread-1消費了的時候,會調用notifyAll,此時喚醒全部在鎖池的對象,從新競爭獲取鎖,此時Thread-2又開始生產了測試

這種方法現象在Java中很常見,好比上篇線程池的文章也有提到過,裏面使用的阻塞隊列底層採用也是相似的機制,核心線程不會被回收被掛起,當有任務來時,喚醒線程去執行,有興趣的能夠去看看重走JAVA之路(五):面試又被問線程池原理?教你如何反擊spa

再次總結一下:線程

  • 若是一個線程調用了wait方法,那麼該線程首先須要獲取到這個對象的鎖(換句話說,一個線程若是調用了某個方法的wait方法,那麼該wait方法必須是在synchronized方法中的)
  • 若是一個線程調用了wait方法,那麼當前線程就會釋放掉線程的鎖。(這個是wait和sleep方法不一樣的地方)
  • 在java中一個對象,會有兩個池(鎖池,等待池),若是一個線程調用了wait方法,那麼該線程進入該對象的等待池中(釋放鎖),若是將來的某一刻,另一個線程調用了這個對象的notify方法,或者notifyAll,那麼在等待池中的線程就會起來進入該對象的鎖池中,參與到獲取鎖的競爭當中,若是獲取鎖成功,將沿着wait方法以後的代碼執行(這就是代碼中,使用while來判斷狀態,而不是if)。
  • notify和notifyAll方法並不會去釋放當前鎖對象,而是經過moniterexist來釋放,也就是說,當前所述的代碼塊在執行結束以後,回去釋放掉鎖,只有在鎖被釋放掉以後,等待池中的線程進入到鎖池,去競爭鎖資源,因此通常notifyAll會防止同步代碼的最後邊

咱們知道android是基於消息機制的,像以前的一個問題爲何Looper.loop()死循環不會致使ANR同樣,主線程從隊列中讀取消息,當沒有消息時,主線程阻塞,讓出CPU,當消息隊列中有消息時,喚醒主線程,接着處理數據,因此 Looer.loop()方法可能會引發主線程的阻塞,但只要它的消息循環沒有被阻塞,能一直處理事件就不會產生ANR異常。

3.interrupt() 中止線程

當須要終止一個線程時,Java給咱們提供了2中方法,stop/interrupt,前者已經被廢棄了,也是不提倡調用的,一調用該方法,被stop的線程會立刻會釋放全部獲取的鎖並在線程的run()方法內,任何一點都有可能拋出ThreadDeath Error,包括在catch或finally語句中,那麼很容易照成被同步的數據沒有被正確的處理完,那麼其它線程在讀取時就會獲得髒數據

這裏主要講解interrupt方法, 首先咱們要明白一點:

調用interrupt()方法,馬上改變的是中斷狀態,但若是不是在阻塞態,就不會拋出異常;若是在進入阻塞態後,中斷狀態爲已中斷,就會馬上拋出異常,什麼叫阻塞態呢,大概就是調用了sleep,join,wait這幾個方法,其實在源碼方法註釋上面也能夠看到這些解釋,若是是非阻塞態的話,那其實這個方法是不起做用的,什麼,不信?那來測試下

public class TestClass {
    public static void main(String []agrs){
       Thread thread = new Thread(new Runnable() {
           @Override
           public void run() {
                while (true){
                    System.out.println("run");
                }
           }
       });
       thread.start();
       thread.interrupt();
    }
}
複製代碼
run
run
run
run
run
run
run
複製代碼

果真,是不起做用的,那咱們再加上阻塞狀態sleep,試一下

public class TestClass {
    public static void main(String []agrs){
       Thread thread = new Thread(new Runnable() {
           @Override
           public void run() {
                while (true){
                    System.out.println("run");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException mE) {
                        mE.printStackTrace();
                        return;
                    }
                }
           }
       });
       thread.start();
       thread.interrupt();
    }
}
複製代碼
run
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.example.hik.lib.MyClass$1.run(MyClass.java:12)
	at java.lang.Thread.run(Thread.java:748)
複製代碼

能夠看到,在Thread.sleep的方法,拋出了異常,同時return掉,此時纔是中止了線程,咱們根據捕獲異常實現邏輯,若是沒法肯定邏輯,那就直接拋出,由上層去處理。

4.總結

須要注意的一點,wait/notify是Object的方法,其餘是Thread的方法,由於每一個對象都有內置鎖,主要目的仍是理解下線程中的一些狀態以及阻塞狀態的本質,但願可以幫助到你們,若有疑問或者錯誤,歡迎一塊兒討論

請幫頂 / 評論點贊!由於你的鼓勵是我寫做的最大動力!

相關文章
相關標籤/搜索