Android小知識-Java多線程的基礎知識瞭解下

本平臺的文章更新會有延遲,你們能夠關注微信公衆號-顧林海,包括年末前會更新kotlin由淺入深系列教程,目前計劃在微信公衆號進行首發,若是你們想獲取最新教程,請關注微信公衆號,謝謝!java

十月份離職,在家修養一個多月,這一個多月作了不少事,本身的微信公衆號開通了,博客也換了一種風格,在簡書和掘金分享一些Android方面的小知識,這一個多月看了些書,有技術相關的,也有非技術相關的,忽然間以爲的這種生活也挺不錯的,這五年買了不少書,加起來最起碼有四五箱的書,之前上班忙,只有晚上回來看個一兩個小時,如今閒了,想全天看就全天看,讀書是一生的事,喜歡讀書,這樣不管在何時都會有本身的思考和見地,不會一味地迎合或沉淪,從而失去立場,失去本身。固然在進行自我提高的同時也在看看有沒有一些工做機會,目前剛回到上海,但願本身能找到一家與之奮鬥的公司,一塊兒成長下去。web

閒話就扯到這裏,下面進入正文。編程


在講到多線程有必要了解下什麼是進程,在百度百科上是這麼定義進程的:進程是操做系統結構的基礎;是一次程序的執行;是一個程序及其數據在處理上順序執行時所發生的活動;是程序在一個數據集合上運行的過程,它是系統進行資源分配和調度的一個獨立單位。安全

百度百科對進程的定義比較抽象,舉個例子,咱們在電腦打開一個程序exe,那這個exe就能夠理解成一個進程,進程是受操做系統管理的基本運行單元。那線程又是什麼,線程是在進程中獨立運行的子任務,好比打開騰訊視頻(進程),你一邊在看視頻,一邊在下載視頻,同時在看視頻時數據的傳輸等等,這些同時執行的任務都是線程,利用多線程能夠同一時間內運行更多不一樣種類的任務。微信

public class Client {

    public static void main(String[] args){
        //輸出main
        System.out.println(Thread.currentThread().getName());
    }

}
複製代碼

經過currentThread方法獲取當前的線程名,上面這個程序在main入口函數中打印當前線程的名稱,發現默認就有一個叫作main線程在執行main()方法中的代碼。多線程

在Java中實現多線程編程的方式有兩種,一種是繼承Thread類,另外一種是實現Runnable接口,下面這個程序就使用第一種方式繼承Thread類:ide

public class Task extends Thread {

    @Override
    public void run() {
        super.run();
        System.out.println("執行相關任務");
    }
}
複製代碼

Task類繼承Thread,run方法中打印一句「執行相關任務」。函數

public class Client {

    public static void main(String[] args){
       Thread thread=new Task();
       thread.start();
       System.out.println("任務執行完畢!");
    }

}
複製代碼

在main函數中先建立Task實例並執行Task線程,接着打印「任務執行完畢!」,運行下看看什麼結果。測試

任務執行完畢!
執行相關任務
複製代碼

發現先打印「任務執行完畢!」,後打印「執行相關任務」,也就是在使用多線程時,代碼的運行結果與代碼執行順序或調用順序是無關的。線程是一個子任務,CPU以不肯定的方式,或者說是以隨機的時間來調用線程中的run方法。spa

若是咱們繼承了Thread類,就不能繼承其它類了,Java不支持多繼承,那怎麼辦呢?幸虧Java提供了Runnable接口,接下來看第二種方式實現Runnable接口來建立線程。

public class Task implements Runnable {
    @Override
    public void run() {
        System.out.println("執行相關任務");
    }
}
複製代碼

很簡單,Task類實現了Runnable接口並實現run方法,怎麼使用這個Task,和上面的Client同樣,代碼以下:

public class Client {

    public static void main(String[] args){
       Runnable runnable=new Task();
       Thread thread=new Thread(runnable);
       thread.start();
       System.out.println("任務執行完畢!");
    }

}
複製代碼

在編寫多線程時容易遇到數據共享問題,多個線程能夠訪問一個變量,看下面程序:

public class Task implements Runnable {

    private int mTaskCount=0;

    @Override
    public void run() {
        mTaskCount++;
        System.out.println("執行第"+mTaskCount+"任務");
    }
}
複製代碼

在Task線程中對mTaskCount進行遞增,下面是Client代碼:

public class Client {

    public static void main(String[] args) {
        Runnable runnable = new Task();
        Thread thread_1 = new Thread(runnable);
        Thread thread_2 = new Thread(runnable);
        Thread thread_3 = new Thread(runnable);
        Thread thread_4 = new Thread(runnable);
        thread_1.start();
        thread_2.start();
        thread_3.start();
        thread_4.start();
        System.out.println("任務執行完畢!");
    }

}
複製代碼

輸出以下:

任務執行完畢!
執行第2任務
執行第2任務
執行第3任務
執行第4任務
複製代碼

發現兩個線程都打印了mTaskCount爲2,產生了「非線程安全」問題,非線程安全主要是指多個線程對同一個對象中的同一個實例變量進行操做時會出現值被更改、值不一樣步的狀況,影響程序的執行流程。在某些JVM中,mTaskCount的操做分紅3個步驟,第一取得原有mTaskCount值,第二計算mTaskCount+1,第三對mTaskCount進行賦值;在這3個步驟中,若是遇到多個線程同時訪問,會出現指令重排序的問題,也就是非線程安全問題。那怎麼解決呢?能夠在run方法前加上synchronized關鍵字:

public class Task implements Runnable {

    private int mTaskCount=0;

    @Override
   synchronized public void run() {
        mTaskCount++;
        System.out.println("執行第"+mTaskCount+"任務");
    }
}
複製代碼

這樣輸出時mTaskCount是依次遞增的,在run方法前加上synchronized關鍵字,使多個線程在執行run方法時,以排隊的形式進行處理。當一個線程試圖調用run方法前,先判斷run方法有沒有上鎖,若是上鎖了,說明有其餘線程在執行run方法,必須等其餘線程執行完run方法,加鎖的這段代碼稱爲「互斥區」或「臨界區」。一個線程想要執行同步方法裏的代碼時,須要先獲取鎖,若是獲取不到鎖,須要不斷的嘗試拿這把鎖,直到可以拿到爲止。

接着瞭解下Thread經常使用的幾種方法:

  • isAlive()方法用於判斷當前的線程是否處於活動狀態,活動狀態就是線程已經啓動且還沒有終止,線程處於正在運行或準備開始運行的狀態,就認爲線程是「存活」的。

  • sleep()方法的做用是在指定的毫秒數內讓當前「正在執行的線程」休 眠(暫停執行)。

  • getId()方法的做用是獲取線程的惟一標識。

線程的開啓是如此的簡單,但咱們有時須要在知足必定條件後關閉線程,這時如何去作呢?

能夠經過interrupt()方法來中止線程,但interrupt()方法僅僅是在當前線程中打了一箇中止的標記,並非真的中止線程。在Java的SDK中,Thread提供了兩種方法用於判斷線程的狀態是否是中止,分別是interrupted()方法,用於測試當前線程是否已經中斷,還有一個就是isInterrupted()方法,用於測試線程是否已經中斷。

先看Thread.interrupted()方法的使用:

public class Task implements Runnable {

    @Override
   synchronized public void run() {
        for (int i=0;i<1000;i++){
            System.out.println("i="+i);
        }
    }
}
複製代碼

在Task線程中經過for循環打印0到999。

public class Client {

    public static void main(String[] args) {
        Runnable runnable = new Task();
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(10);
            thread.interrupt();
            System.out.println(Thread.interrupted());
            System.out.println(Thread.interrupted());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
複製代碼

啓動Task線程後,執行暫停10毫秒後調用interrupt()方法,最後打印出Thread.interrupted()方法兩次,咱們看打印結果:

i=0
i=1
i=2
...
i=248
false
false
i=249
...
i=998
i=999
複製代碼

經過interrupt()方法並不能中止Task線程,而執行Thread.interrupted()方法,輸出兩次都爲false,也就是說Thread.interrupted()方法是用於測試當前線程是否已經中斷,這個當前線程指的是main線程,它從未中斷過,因此打印的結果是兩個false,這裏先看如何使main線程產生中斷效果,看下面代碼:

public class Client {

    public static void main(String[] args) {
        Thread.currentThread().interrupt();
        System.out.println(Thread.interrupted());
        System.out.println(Thread.interrupted());
    }

}
複製代碼

打印:

true
false
複製代碼

經過Thread.currentThread().interrupt()給當前main線程打上中止的標誌,那爲何第二次輸出Thread.interrupted()方法時是false呢?官方文檔對interrupted()方法的解釋以下:測試當前線程是否已經中斷。線程的中斷狀態由該方法清除。換句話說,若是連續兩次調用該方法,則第二次調用將返回false。也就說interrupted()方法具備清除狀態的功能。

isInterrupted()方法與interrupted()方法相比,isInterrupted()方法並不具備清除狀態,也就是咱們給Task線程執行interrupt()方法後,Task線程就被打上了中斷狀態,無論執行多少次isInterrupted()方法都會返回true。

既然知道了interrupt()的做用,若是先執行task線程的interrupt()方法,這時Task線程被打上中斷狀態,而後再在Task的run方法中經過判斷Thread.interrupted()是否爲true,若是爲true就退出循環,代碼以下:

public class Task implements Runnable {

    @Override
   synchronized public void run() {

        for (int i=0;i<1000;i++){
            if(Thread.interrupted()){
                break;
            }
            System.out.println("i="+i);
        }
    }
}
複製代碼

Client代碼以下:

public class Client {

    public static void main(String[] args) {
        Runnable runnable = new Task();
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(10);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
複製代碼

打印:

i=0
i=1
i=2
...
i=284
i=285
i=286
i=287
i=288
複製代碼

這樣不就能夠在外部中斷了Task線程,這種方式雖然能夠中止了Task線程,但若是在for語句下打印一句話,代碼:

public class Task implements Runnable {

    @Override
   synchronized public void run() {

        for (int i=0;i<1000;i++){
            if(Thread.interrupted()){
                break;
            }
            System.out.println("i="+i);
        }
        System.out.println("不該該打印");
    }
}
複製代碼

打印:

i=0
i=1
i=2
...
i=223
i=224
i=225
不該該打印
複製代碼

發現for循環語句下面的的println仍是打印出來了,這時能夠在判斷Thread.interrutped()語句中經過拋出異常來退出,代碼以下:

public class Task implements Runnable {

    @Override
    synchronized public void run() {
        try {
            for (int i = 0; i < 1000; i++) {
                if (Thread.interrupted()) {
                    throw new InterruptedException();
                }
                System.out.println("i=" + i);
            }
            System.out.println("不該該打印");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
複製代碼

打印:

i=0
i=1
i=2
...
i=106
i=107
i=108
java.lang.InterruptedException
    at com.book.demo.demo01.Task.run(Task.java:10)
    at java.lang.Thread.run(Thread.java:745)
複製代碼

這種方式叫作異常法退出。

固然也能夠經過return來退出線程:

public class Task implements Runnable {

    @Override
    synchronized public void run() {
        for (int i = 0; i < 1000; i++) {
            if (Thread.interrupted()) {
                return;
            }
            System.out.println("i=" + i);
        }
        System.out.println("不該該打印");

    }
}
複製代碼

關於多線程的相關知識後面還有不少相關文章,等不及的能夠在微信公衆號上查看,謝謝!


838794-506ddad529df4cd4.webp.jpg
相關文章
相關標籤/搜索