Java 多線程系列文章第 5 篇。java
試想一下沒有線程的程序是怎麼樣的?百度網盤在上傳文件時就沒法下載文件了,得等文件上傳完成後才能下載文件。這個咱們如今看起來很反人性,由於咱們習慣了一個程序同時能夠進行運行多個功能,而這些都是線程的功勞。設計模式
以前的文章 進程知多少 中講到,爲了實現多個程序並行執行,引入了進程概念。如今引入線程是爲了讓一個程序可以併發執行。bash
線程ID:線程標識符。多線程
當前指令指針(PC):指向要執行的指令。併發
寄存器集合:存儲單元寄存器的集合。異步
堆棧:暫時存放數據和地址,通常用來保護斷點和現場。ide
線程和進程之間的區別,我以爲能夠用這個例子來看出二者的不一樣,進程就是一棟房子,房子住着 3 我的,線程就是住在房子裏的人。進程是一個獨立的個體,有本身的資源,線程是在進程裏的,多個線程共享着進程的資源。函數
咱們看到 Java 源代碼裏面,線程狀態的枚舉有以下 6 個。工具
public enum State {
//新建狀態
NEW,
//運行狀態
RUNNABLE,
//阻塞狀態
BLOCKED,
//等待狀態
WAITING,
//等待狀態(區別在於這個有等待的時間)
TIMED_WAITING,
//終止狀態
TERMINATED;
}
複製代碼
下面給這 6 個狀態一一作下解釋。spa
NEW:新建狀態。在建立完 Thread ,還沒執行 start() 以前,線程的狀態一直是 NEW。能夠說這個時候尚未真正的一個線程映射着,只是一個對象。
RUNNABLE:運行狀態。線程對象調用 start() 以後,就進入 RUNNABLE 狀態,該狀態說明在 JVM 中有一個真實的線程存在。
BLOCKED:阻塞狀態。線程在等待鎖的釋放,也就是等待獲取 monitor 鎖。
WAITING:等待狀態。線程在這個狀態的時候,不會被分配 CPU,並且須要被顯示地喚醒,不然會一直等待下去。
TIMED_WAITING:超時等待狀態。這個狀態的線程也同樣不會被分配 CPU,可是它不會無限等待下去,有時間限制,時間一到就中止等待。
TERMINATED:終止狀態。線程執行完成結束,但不表明這個對象已經沒有了,對象可能仍是存在的,只是線程不存在了。
線程既然有這麼多個狀態,那確定就有狀態機,也就是在什麼狀況下 A 狀態會變成 B 狀態。下面就來簡單描述一下。
結合下圖,咱們 new 出線程類的時候,就是 NEW
狀態,調用 start() 方法,就進入了 RUNNABLE
狀態,這時若是觸發等待,則進入了 WAITING
狀態,若是觸發超時等待,則進入 TIMED_WAITING
狀態,當訪問須要同步的資源時,則只有一個線程能訪問,其餘線程就進入 BLOCKED
狀態,當線程執行完後,進入 TERMINATED
狀態。
其實在 JVM 中,線程是有 9 個狀態,以下所示,有興趣的同窗能夠深刻了解一下。
javaClasses.hpp
enum 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
};
複製代碼
下面講一講在 Java 中如何建立一個線程。衆所周知,實現 Java 線程有 2 種方式:繼承 Thread 類和實現 Runnable 接口。
繼承 Thread 類,重寫 run()
方法。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread");
}
}
複製代碼
實現 Runnable 接口,實現 run()
方法。
class MyRunnable implements Runnable {
public void run() {
System.out.println("MyRunnable");
}
}
複製代碼
這 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");
}
}
複製代碼
打印結果以下:
main Thread begin
main Thread end
MyThread
MyRunnable
複製代碼
看這結果,不像我們以前的串行執行依次打印,主線程不會等待子線程執行完。
敲重點:不能直接調用 run()
,直接調用 run()
不會建立線程,而是主線程直接執行 run()
的內容,至關於執行普通函數。這時就是串行執行的。看下面代碼。
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");
}
}
複製代碼
打印結果:
main Thread begin
MyThread
MyRunnable
main Thread end
複製代碼
從結果看出只是串行的,但看不出沒有線程,咱們看下面例子來驗證直接調用 run()
方法沒有建立新的線程,使用 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();
}
}
}
複製代碼
運行結果:
main Thread begin
MyThread
複製代碼
只打印出 2 句日誌,觀察線程時也只看到 main
線程,沒有看到 MyThread
和 MyRunnable
線程,印證了上面我們說的:直接調用 run()
方法,沒有建立線程。
下面咱們來看看有 調用 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();
}
}
}
複製代碼
運行結果:
main Thread begin
main Thread end
MyThread
MyRunnable
複製代碼
全部日誌都打印出來了,而且經過 VisualVM 工具能夠看到 MyThread
和 MyRunnable
線程。看到了這個結果,切記建立線程要調用 start()
方法。
今天就先講到這,繼續關注後面的內容。
推薦閱讀
後臺回覆『設計模式』能夠獲取《一故事一設計模式》電子書
以爲文章有用幫忙轉發&點贊,多謝朋友們!