做爲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
再次總結一下:線程
咱們知道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的方法,由於每一個對象都有內置鎖,主要目的仍是理解下線程中的一些狀態以及阻塞狀態的本質,但願可以幫助到你們,若有疑問或者錯誤,歡迎一塊兒討論