1. 什麼是線程和進程?
在早期的計算機中不包含操作系統,它們從頭到尾只執行一個程序,並肯這個程序能訪問計算機中的所有的所有資源。在這種裸機環境中,不僅很難編寫和運行程序,而且每次只能運行一個程序,這對於昂貴並且稀有的計算機資源來說也是一種浪費。
操作系統的出現使得計算機每次能運行多個程序,並且不同的程序都在單獨的進程中運行:操作系統爲各個獨立執行的進程分配各種資源,包括內存,文件句柄以及安全證書等。如果需要的話,在不同的進程之間可以通過一些粗粒度的通信機制來交換數據,包括:套接字、信號處理器、共享內存、信號量以及文件等。
1.1 什麼是進程
進程是程序的一次執行過程,是系統運行程序的基本單位,因此進程是動態的。系統運行一個程序即是一個進程從創建,運行到消亡的過程。
在 Java 中,當我們啓動 main 函數時其實就是啓動了一個 JVM 的進程,而 main 函數所在的線程就是這個進程中的一個線程,也稱主線程。
如下圖所示,在 windows 中通過查看任務管理器的方式,我們就可以清楚看到 window 當前運行的進程(.exe 文件的運行)。
1.2 什麼是線程
線程與進程相似,但線程是一個比進程更小的執行單位。一個進程在其執行的過程中可以產生多個線程。與進程不同的是同類的多個線程共享進程的堆和方法區資源,但每個線程有自己的程序計數器、虛擬機棧和本地方法棧,所以系統在產生一個線程,或是在各個線程之間作切換工作時,負擔要比進程小得多,也正因爲如此,線程也被稱爲輕量級進程。
2. Java中創建線程
在Java中創建線程的方式有三種:繼承Thread類重寫run方法、實現runnable接口重寫run方法和實現callable接口重寫call方法配合futureTask使用
2.1 繼承Thread類
繼承Thread類的方式創建線程算是最簡單的了,但是你的線程類往往要繼承項目中的其他類,而Java是單繼承機制的,所以使用此方法會有很大的侷限性。
package com.yuanxw.chapter1;
/**
* Java中創建線程一:繼承Thread類重寫run方法
*/
public class MyThread extends Thread {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println(String.format("線程:【%s】,運行結束", currentThread().getName()));
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
System.out.println(String.format("線程:【%s】,打印:%s", currentThread().getName(),i));
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(String.format("線程:【%s】,運行結束", currentThread().getName()));
}
}
2.2 實現Runnable接口
Thread類的構造方法允許傳入一個實現Runnable接口的target進去,線程啓動將會執行target.run方法。實現Runnable接口的方式可以很好的避免單繼承問題。
package com.yuanxw.chapter1;
/**
* Java中創建線程二:實現Runnable接口重寫run方法
*/
public class MyRunnable implements Runnable{
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
System.out.println(String.format("線程:【%s】,運行結束", Thread.currentThread().getName()));
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
System.out.println(String.format("線程:【%s】,打印:%s", Thread.currentThread().getName(),i));
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(String.format("線程:【%s】,運行結束", Thread.currentThread().getName()));
}
}
2.3 實現Callable接口
call方法與run方法最大的區別在於call方法存在返回值futureTask的get方法可以獲取這個返回值。使用此種方法實現線程的好處是當你創建的任務的結果不是立即就要時,你可以提交一個線程在後臺執行,而你的程序仍可以正常運行下去,在需要執行結果時使用futureTask去獲取即可。這是一種典型的異步任務處理的方法。
package com.yuanxw.chapter1;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeoutException;
public class MyCallable implements Callable {
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
FutureTask futureTask1 = new FutureTask(new MyCallable());
new Thread(futureTask1,"子線程-A").start();
FutureTask futureTask2 = new FutureTask(new MyCallable());
new Thread(futureTask2,"子線程-B").start();
// futureTask可以在指定時間內獲取線程執行的返回值,超時則丟棄任務
// 因此futureTask可以用作異步任務處理
// futureTask.get(1000, TimeUnit.SECONDS);
System.out.println(String.format("線程:【%s】,運行結束", Thread.currentThread().getName()));
}
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i < 50; i++) {
try {
System.out.println(String.format("線程:【%s】,打印:%s", Thread.currentThread().getName(),i));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
sum += i;
}
System.out.println(String.format("線程:【%s】,運行結束", Thread.currentThread().getName()));
System.out.println("執行結果:" + sum);
return sum;
}
}
3. 線程的生命週期和狀態
Java 線程在運行的生命週期中的指定時刻只可能處於下面 6 種不同狀態的其中一個狀態(圖源《Java 併發編程藝術》4.1.4 節)。
線程在生命週期中並不是固定處於某一個狀態而是隨着代碼的執行在不同狀態之間切換。Java 線程狀態變遷如下圖所示(圖源《Java 併發編程藝術》4.1.4 節):
由上圖可以看出:線程創建之後它將處於 NEW(新建) 狀態,調用 start() 方法後開始運行,線程這時候處於 READY(可運行) 狀態。可運行狀態的線程獲得了 CPU 時間片(timeslice)後就處於 RUNNING(運行) 狀態。
當線程執行 wait()方法之後,線程進入 WAITING(等待) 狀態。進入等待狀態的線程需要依靠其他線程的通知才能夠返回到運行狀態,而 TIME_WAITING(超時等待) 狀態相當於在等待狀態的基礎上增加了超時限制,比如通過 sleep(long millis)方法或 wait(long millis)方法可以將 Java 線程置於 TIMED WAITING 狀態。當超時時間到達後 Java 線程將會返回到 RUNNABLE 狀態。當線程調用同步方法時,在沒有獲取到鎖的情況下,線程將會進入到 BLOCKED(阻塞) 狀態。線程在執行 Runnable 的run()方法之後將會進入到 TERMINATED(終止) 狀態。
學習推薦書籍:
[1]: 《Java併發編程實踐 》
[2]: 《Java併發編程的藝術 》
[3]: 《Java高併發編程詳解:多線程與架構設計》
[4]: 《Java多線程編程核心技術(第2版)》