面試中經常被問到:
什麼是進程?什麼是線程?兩者的區別?
感覺這個問題網上有很多版本,So我把它們收集起來,黑體句子是我現在這個水平贊同的說法。
1.進程是資源分配的最小單位(操作系統教材裏的說法);是正在運行的程序實例;是構成運行程序的資源的集合(C#圖解教程裏的說法);每個正在系統上運行的程序都是一個進程;
2.線程是CPU調度的最小單位(同操作系統教材裏的說法);是一組指令的集合,或者是程序的特殊段,它可以在程序裏獨立執行;也可以把它理解爲代碼運行的上下文。
3.兩者關係:默認情況下,一個進程中只包含一個線程(主線程)且至少有一個線程,但一個進程可以包含多個線程。線程基本上是輕量級的進程,進程是所有線程的集合,每一個線程是進程中的一條執行路徑。
爲什麼用到多線程(多線程的好處)? 提高程序效率和響應性,充分利用計算資源(多核cpu)。舉例: 迅雷多線程下載、分批發送短信等。
多線程不好的地方:首先會帶來線程安全問題,線程切換會帶來一定的系統消耗。
Java中(多)線程創建方式:
①繼承Thread類(不推薦,因爲Java不支持多繼承,繼承會讓類間的耦合性變高):
public class Main {
public static void main(String[] args) {
// 創建線程
Thread myThread = new MyThread();
// 啓動線程(不要直接調用run方法,不然程序就是線性執行了)
myThread.start();
// 輸出"當前線程"的線程名稱
System.out.printf("1.Welcome! I'm %s\n", Thread.currentThread().getName());
}
}
// 定義繼承Thread類的子類
class MyThread extends Thread {
// 複寫run方法,方法中實現了線程的任務處理邏輯
@Override
public void run() {
System.out.printf("2.Welcome! I'm %s\n", Thread.currentThread().getName());
}
}
②實現Runnable接口(這種方式比較推薦,面向接口編程):
public class Main {
public static void main(String[] args) {
// 創建線程
Thread myThread = new Thread(new MyThread());
// 啓動線程(不要直接調用run方法,不然程序就是線性執行了)
myThread.start();
// 輸出"當前線程"的線程名稱
System.out.printf("1.Welcome! I'm %s\n", Thread.currentThread().getName());
}
}
// 定義繼承Thread類的子類
class MyThread implements Runnable {
// 複寫run方法,方法中實現了線程的任務處理邏輯
@Override
public void run() {
System.out.printf("2.Welcome! I'm %s\n", Thread.currentThread().getName());
}
}
值得一提的是,主線程和子線程的執行順序並不是固定了,和寫的順序無關。比如上面的例子,main和Thread-0的打印順序並不是固定的,這個和操作系統的調度有關。
③匿名內部類(這種和第二種類似,優點是少聲明類):
public class Main {
public static void main(String[] args) {
Thread myThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 10; i > 0; i--) {
System.out.println("sub:" + i);
int n = 10 / i;
}
}
});
myThread.start();
for (int i = 10; i > 0; i--) {
System.out.println("main:" + i);
int n = 10 / i;
}
}
}
也可以用Java8的lambda表達式創建:
Thread myThread = new Thread(()-> {
for (int i = 10; i > 0; i--) {
System.out.println("sub:" + i);
int n = 10 / i;
}
});
如果多運行幾次,會發現每次結果都不一樣,而且大多數情況main和sub交替執行,這就和之前的順序執行區別開來。
下面說明線程間不受影響性。將main線程和子線程中循環變量i的的終止條件都改爲i > -10,讓子線程運行到半路拋異常。運行程序後發現子線程運行到i = 0就退出了,而main線程不受影響:
public class Main {
public static void main(String[] args) {
Thread myThread = new Thread(()-> {
for (int i = 10; i > -10; i--) {
System.out.println("sub:" + i);
int n = 10 / i;
}
});
myThread.start();
for (int i = 10; i > -10; i--) {
System.out.println("main:" + i);
// int n = 10 / i;
}
}
}
分析運行結果可以知道,當sub中的i減小到0後,main線程佔用cpu,一直減少到3,然後sub線程又拿到cpu,10 / 0拋出異常,後續的代碼停止執行。而主線程繼續執行到循環體結束。如果在main線程中去掉註釋,在sub線程中註釋,結果就反過來,main線程執行到一半卡殼,而sub線程繼續執行完。這一定程度上體現了線程間互不干擾性。
④線程池技術(線程池是管理併發執行任務個數的理想方法;當你有多個任務需要創建多個線程,考慮使用線程池,更加高效)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.execute(new MyThread());
executor.execute(new MyThread());
executor.execute(new MyThread());
executor.shutdown();
for (int i = 5; i > 0; i--) {
System.out.println("main:" + i);
}
}
}
// 定義繼承Thread類的子類
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 5; i > 0; i--) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
這只是簡單使用了一下線程池技術,更多用法參考Oracle的jdk文檔。