(一)初識線程

多任務執行的演變

玩電腦的時候,可以一邊聽歌,一邊聊天,電腦可以同時做很多事,多個任務宏觀上同時在執行。

  • 單核CPU時代:
    • 一個任務的時候,在一個時間點只能執行一個任務。
    • 多個任務的時候,“同一個時間點”執行多個任務,並非真正意義的同個時間點,而是由操作系統不斷切換時間片
      (不斷切換任務佔領CPU),讓每個任務都有一定時間可以運行。由於這個時間片特別短,所以我們幾乎感受不出來
  • 多核CUP時代:真正的不同線程可以被不同CPU核執行,也就是可能同時在操作一個內存數據,如果使用不好,
    這種行爲導致結果可能是不可預測的。(一個線程A寫操作改了數據,但是另一個線程B不知道A改了數據,
    讀取出了這個數據,卻發現不是自己想要的)

線程和進程的區別

  • 進程:操作系統資源分配的基本單位,打開電腦的任務管理器就可以看到很多進程,每一個程序(比如QQ)一般不止一個進程。

  • 線程:任務調度和執行的基本單位,一個進程可能有很多線程,一個有效的進程至少有一個線程。每一個線程都有自己的局部變量表,
    程序計數器(指向正在執行的指令指針)。

  • 進程有自己的內存地址空間,線程沒有,被包含在進程的地址空間中。

  • 進程最少有一條線程,線程可以看成是輕量級的進程。

  • 本質區別:進程擁有自己的一整套變量,而線程則共享數據,撤銷或者啓動一個線程的成本更低。

爲什麼需要多線程

在平時下載東西的時候,我們可能需要同時做點其他東西,加入一個音樂軟件只能有一個線程,那麼下載的時候,我們就不能同時聽音樂了,說更嚴重一些,如果下載的時候網路比較慢,阻塞了,我們想點擊按鈕讓它停下來,也是不能的。

如果一個線程,我們模擬一下聽歌又看書的情景,我們會發現永遠不會輸出看書。

public class TryConcurrent {
    public static void main(String[] args) {
        listenToMusic();
        readBook();
    }
    public static void listenToMusic(){
        for(;;){
            System.out.println("I am listening to music!");
        }
    }

    public static void readBook(){
        for(;;) {
            System.out.println("I am reading a book!");
        }
    }
}

輸出結果:

I am listening to music!
I am listening to music!
I am listening to music!
I am listening to music!
I am listening to music!
I am listening to music!
...

啓動多線程

於是我們使用new Thread來創建一個新的線程,如下:

public class TryConcurrent {
    public static void main(String[] args) {
        listenToMusic();
        new Thread(new Runnable() {
            @Override
            public void run() {
                readBook();
            }
        }).start();
    }
    public static void listenToMusic(){
        for(;;){
            System.out.println("I am listening to music!");
        }
    }

    public static void readBook(){
        for(;;) {
            System.out.println("I am reading a book!");
        }
    }
}

但是這樣也得不到我們想要的結果,也是會一直輸出聽音樂,這是爲什麼呢?明明後面啓動了新的線程了。
上面代碼通過匿名內部類的方式創建線程並且重寫了run方法。
啓動新線程必須在其中一個任務之前,因爲啓動線程本身是依賴於主線程的,也就是主線程的聽音樂一直不結束的話,就永遠執行不到新建線程那一步,自然也就不會有readbook輸出。
其中,調用start方法,纔是真正的派生了一個新的線程,否則只是一個簡單的Thread對象,start方法是一個立即返回的方法,也就是不管線程裏面邏輯多複雜,都是會執行後面的方法的,因爲裏面的邏輯歸新創建的線程,而不是當前線程。
如果使用java 8的Lambda屬性的話,可以直接寫成new Thread(TryConcurrent::readBook).start();

在運行的時候,我們可以使用Jconsole來觀察線程,只要在cmd輸入Jconsole即可,或者Jstack也是可以的,都是由JDK提供的。

選擇自己要監控的進程,點進去就可以看到線程頁面的一些信息了。

其實我們可以看到並不只有main線程和自己新建的線程Thread-0,還有一些守護線程,比如垃圾回收線程等等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章