电竞比分网-中国电竞赛事及体育赛事平台

分享

線程最最基礎的知識

 佬總圖書管 2019-09-25

在這里插入圖片描述

Java 多線程系列文章第 5 篇。

什么是線程

試想一下沒有線程的程序是怎么樣的?百度網(wǎng)盤在上傳文件時就無法下載文件了,得等文件上傳完成后才能下載文件。這個我們現(xiàn)在看起來很反人性,因為我們習慣了一個程序同時可以進行運行多個功能,而這些都是線程的功勞。

之前的文章 進程知多少 中講到,為了實現(xiàn)多個程序并行執(zhí)行,引入了進程概念?,F(xiàn)在引入線程是為了讓一個程序能夠并發(fā)執(zhí)行。

線程的組成

線程ID:線程標識符。

當前指令指針(PC):指向要執(zhí)行的指令。

寄存器集合:存儲單元寄存器的集合。

堆棧:暫時存放數(shù)據(jù)和地址,一般用來保護斷點和現(xiàn)場。

線程與進程區(qū)別

線程和進程之間的區(qū)別,我覺得可以用這個例子來看出兩者的不同,進程就是一棟房子,房子住著 3 個人,線程就是住在房子里的人。進程是一個獨立的個體,有自己的資源,線程是在進程里的,多個線程共享著進程的資源。

線程狀態(tài)

我們看到 Java 源代碼里面,線程狀態(tài)的枚舉有如下 6 個。

public enum State { //新建狀態(tài) NEW, //運行狀態(tài) RUNNABLE, //阻塞狀態(tài) BLOCKED, //等待狀態(tài) WAITING, //等待狀態(tài)(區(qū)別在于這個有等待的時間) TIMED_WAITING, //終止狀態(tài) TERMINATED;}
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

下面給這 6 個狀態(tài)一一做下解釋。

NEW:新建狀態(tài)。在創(chuàng)建完 Thread ,還沒執(zhí)行 start() 之前,線程的狀態(tài)一直是 NEW??梢哉f這個時候還沒有真正的一個線程映射著,只是一個對象。

RUNNABLE:運行狀態(tài)。線程對象調用 start() 之后,就進入 RUNNABLE 狀態(tài),該狀態(tài)說明在 JVM 中有一個真實的線程存在。

BLOCKED:阻塞狀態(tài)。線程在等待鎖的釋放,也就是等待獲取 monitor 鎖。

WAITING:等待狀態(tài)。線程在這個狀態(tài)的時候,不會被分配 CPU,而且需要被顯示地喚醒,否則會一直等待下去。

TIMED_WAITING:超時等待狀態(tài)。這個狀態(tài)的線程也一樣不會被分配 CPU,但是它不會無限等待下去,有時間限制,時間一到就停止等待。

TERMINATED:終止狀態(tài)。線程執(zhí)行完成結束,但不代表這個對象已經(jīng)沒有了,對象可能還是存在的,只是線程不存在了。

線程既然有這么多個狀態(tài),那肯定就有狀態(tài)機,也就是在什么情況下 A 狀態(tài)會變成 B 狀態(tài)。下面就來簡單描述一下。

結合下圖,我們 new 出線程類的時候,就是 NEW 狀態(tài),調用 start() 方法,就進入了 RUNNABLE 狀態(tài),這時如果觸發(fā)等待,則進入了 WAITING 狀態(tài),如果觸發(fā)超時等待,則進入 TIMED_WAITING 狀態(tài),當訪問需要同步的資源時,則只有一個線程能訪問,其他線程就進入 BLOCKED 狀態(tài),當線程執(zhí)行完后,進入 TERMINATED 狀態(tài)。
在這里插入圖片描述
其實在 JVM 中,線程是有 9 個狀態(tài),如下所示,有興趣的同學可以深入了解一下。

javaClasses.hppenum ThreadStatus {    NEW = 0,    RUNNABLE = JVMTI_THREAD_STATE_ALIVE + // runnable / running                               JVMTI_THREAD_STATE_RUNNABLE,    SLEEPING = JVMTI_THREAD_STATE_ALIVE + // Thread.sleep()                               JVMTI_THREAD_STATE_WAITING +                               JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT +                               JVMTI_THREAD_STATE_SLEEPING,    IN_OBJECT_WAIT = JVMTI_THREAD_STATE_ALIVE + // Object.wait()                               JVMTI_THREAD_STATE_WAITING +                               JVMTI_THREAD_STATE_WAITING_INDEFINITELY +                               JVMTI_THREAD_STATE_IN_OBJECT_WAIT,    IN_OBJECT_WAIT_TIMED = JVMTI_THREAD_STATE_ALIVE + // Object.wait(long)                               JVMTI_THREAD_STATE_WAITING +                               JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT +                               JVMTI_THREAD_STATE_IN_OBJECT_WAIT,    PARKED = JVMTI_THREAD_STATE_ALIVE + // LockSupport.park()                               JVMTI_THREAD_STATE_WAITING +                               JVMTI_THREAD_STATE_WAITING_INDEFINITELY +                               JVMTI_THREAD_STATE_PARKED,    PARKED_TIMED = JVMTI_THREAD_STATE_ALIVE + // LockSupport.park(long)                               JVMTI_THREAD_STATE_WAITING +                               JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT +                               JVMTI_THREAD_STATE_PARKED,    BLOCKED_ON_MONITOR_ENTER = JVMTI_THREAD_STATE_ALIVE + // (re-)entering a synchronization block                               JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER,    TERMINATED = JVMTI_THREAD_STATE_TERMINATED};
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

  • 25

  • 26

  • 27

  • 28

  • 29

Java 線程實現(xiàn)

下面講一講在 Java 中如何創(chuàng)建一個線程。眾所周知,實現(xiàn) Java 線程有 2 種方式:繼承 Thread 類和實現(xiàn) Runnable 接口。

繼承 Thread 類

繼承 Thread 類,重寫 run() 方法。

class MyThread extends Thread { @Override public void run() { System.out.println('MyThread'); }}
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

實現(xiàn) Runnable 接口

實現(xiàn) Runnable 接口,實現(xiàn) run() 方法。

class MyRunnable implements Runnable {    public void run() {        System.out.println('MyRunnable');    }}
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

這 2 種線程的啟動方式也不一樣。MyThread 是一個線程類,所以可以直接 new 出一個對象出來,接著調用 start() 方法來啟動線程;而 MyRunnable 只是一個普通的類,需要 new 出線程基類 Thread 對象,將 MyRunnable 對象傳進去。

下面是啟動線程的方式。

public class ThreadImpl { public static void main(String[] args) { MyThread myThread = new MyThread(); Thread myRunnable = new Thread(new MyRunnable()); System.out.println('main Thread begin'); myThread.start(); myRunnable.start(); System.out.println('main Thread end'); }}
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

打印結果如下:

main Thread beginmain Thread endMyThreadMyRunnable
  • 1

  • 2

  • 3

  • 4

看這結果,不像咱們之前的串行執(zhí)行依次打印,主線程不會等待子線程執(zhí)行完。

敲重點:不能直接調用 run(),直接調用 run() 不會創(chuàng)建線程,而是主線程直接執(zhí)行 run() 的內容,相當于執(zhí)行普通函數(shù)。這時就是串行執(zhí)行的??聪旅娲a。

public class ThreadImpl { public static void main(String[] args) { MyThread myThread = new MyThread(); Thread myRunnable = new Thread(new MyRunnable()); System.out.println('main Thread begin'); myThread.run(); myRunnable.run(); System.out.println('main Thread end'); }}
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

打印結果:

main Thread beginMyThreadMyRunnablemain Thread end
  • 1

  • 2

  • 3

  • 4

從結果看出只是串行的,但看不出沒有線程,我們看下面例子來驗證直接調用 run() 方法沒有創(chuàng)建新的線程,使用 VisualVM 工具來觀察線程情況。

我們對代碼做一下修改,加上 Thread.sleep(1000000) 讓它睡眠一段時間,這樣方便用工具查看線程情況。

調用 run() 的代碼:

public class ThreadImpl { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.setName('MyThread'); Thread myRunnable = new Thread(new MyRunnable()); myRunnable.setName('MyRunnable'); System.out.println('main Thread begin'); myThread.run(); myRunnable.run(); System.out.println('main Thread end'); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } }}class MyThread extends Thread { @Override public void run() { System.out.println('MyThread'); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } }}class MyRunnable implements Runnable { public void run() { System.out.println('MyRunnable'); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } }}
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

  • 25

  • 26

  • 27

  • 28

  • 29

  • 30

  • 31

  • 32

  • 33

  • 34

  • 35

  • 36

  • 37

  • 38

  • 39

  • 40

  • 41

  • 42

  • 43

  • 44

  • 45

  • 46

運行結果:

main Thread beginMyThread
  • 1

  • 2

在這里插入圖片描述
只打印出 2 句日志,觀察線程時也只看到 main 線程,沒有看到 MyThreadMyRunnable 線程,印證了上面咱們說的:直接調用 run() 方法,沒有創(chuàng)建線程

下面我們來看看有
調用 start() 的代碼:

public class ThreadImpl { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.setName('MyThread'); Thread myRunnable = new Thread(new MyRunnable()); myRunnable.setName('MyRunnable'); System.out.println('main Thread begin'); myThread.start(); myRunnable.start(); System.out.println('main Thread end'); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } } }
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

運行結果:

main Thread beginmain Thread endMyThreadMyRunnable
  • 1

  • 2

  • 3

  • 4

在這里插入圖片描述
所有日志都打印出來了,并且通過 VisualVM 工具可以看到 MyThreadMyRunnable 線程??吹搅诉@個結果,切記創(chuàng)建線程要調用 start() 方法。

今天就先講到這,繼續(xù)關注后面的內容。

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多