感覺自己java基礎知識學的太差,記錄一下自己學習 深入淺出多執行緒 的筆記。
筆記連線:
https://flowus.cn/share/d4a68486-0162-4759-816e-551b275218f3
【FlowUs 息流】
1.程式和執行緒基本概念
程式產生的背景: 最初計算機只能輸入一次指令然後執行一次,效率較低。後來有了批處理系統,使用者可以將一串的指令交給計算機,由計算機依次處理。但是還是序列處理,隨程式的阻塞而阻塞。
為了解決這個問題,出現了程式的概念:指正在執行的一個程式或應用程式,記憶體中可以存在多個程式。CPU採用時間片輪轉的方式執行程式。 每個程式的時間片結束後,把CPU分配給下一個程式,假如當前程式任務沒結束,則儲存當前程式的資訊,等待下一次分配;
然而,人們並不滿足於此。如果一個程式包含多個子任務,程式也只能逐個執行,效率不高。
那麼能不能讓這些子任務同時執行呢?於是人們又提出了執行緒的概念,那麼能不能讓這些子任務同時執行呢?於是人們又提出了執行緒的概念,讓一個執行緒執行一個子任務,這樣一個程式就包含了多個執行緒,每個執行緒負責一個單獨的子任務。
例如: 一個下載軟體可以使用多個執行緒同時下載多個檔案,從而大大縮短下載時間。
執行緒和程式:
2. Thread類和Runnable介面
自定義Thread類:
public class Demo {
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread");
}
}
public static void main(String[] args) {
Thread myThread = new MyThread();
myThread.start();
}
}
start()
方法後,該執行緒才算啟動- 呼叫了start()方法後,虛擬機器會建立一個執行緒,然後等到這個執行緒第一次得到時間片時再呼叫run()方法。
- 第二次呼叫start()方法會丟擲IllegalThreadStateException異常。、
Thread
類是一個 Runnable
介面的實現類。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Runnable介面只有一個 run 方法, 並且有 @FunctionalInterface 註解。意思是:函式式介面, 介面裡面只能有一個抽象方法
說明我們可以使用 Lambda 表示式
public static void main(String[] args) {
new Thread(new MyThread()).start();
// Java 8 函數語言程式設計,可以省略MyThread類
new Thread(() -> {
System.out.println("Java 8 匿名內部類");
}).start();
}
2.1 執行緒優先順序
- 每個Java程式都有一個預設的主執行緒,就是透過JVM啟動的第一個執行緒main執行緒。
- Java中執行緒優先順序可以指定,範圍是1~10。Java預設的執行緒優先順序為5,
- Java中的優先順序來說不是特別的可靠,Java程式中對執行緒所設定的優先順序只是給作業系統一個建議,作業系統不一定會採納。而真正的呼叫順序,是由作業系統的執行緒排程演算法決定的。
例如我們可以使用如下的程式碼測試一下:
public static class T1 extends Thread {
@Override
public void run() {
super.run();
System.out.println(String.format("當前執行的執行緒是:%s,優先順序:%d",
Thread.currentThread().getName(),
Thread.currentThread().getPriority()));
}
}
@Test
void contextLoads() throws ExecutionException, InterruptedException {
IntStream.range(1, 10).forEach(i -> {
Thread thread = new Thread(new T1());
thread.setPriority(i);
thread.start();
});
}
可以看到不完全按照優先順序來, 但是大致差不多。
3. 執行緒組(ThreadGroup)
Java中用ThreadGroup來表示執行緒組,我們可以使用執行緒組對執行緒進行批次控制。
ThreadGroup是一個標準的向下引用的樹狀結構。
頂層的 ThreadGroup 被稱為系統執行緒組(system thread group),由 JVM 執行時建立並擁有它。
除了系統執行緒組外,每個 ThreadGroup 物件都有一個父 ThreadGroup ,即直接包含它的那個 ThreadGroup 。
為什麼這麼設計呢?
節省資源:根據樹形結構,可以很容易遞迴回收該 ThreadGroup 中的執行緒或執行緒組,避免了記憶體洩漏和資源浪費等問題。
確保執行緒安全:透過 ThreadGroup 可以限制某些執行緒的執行許可權,比如可以設定特定的許可權來防止執行緒意外獲得系統特權,確保執行緒不會危及系統安全。可以透過重寫 ThreadGroup 的 checkAccess 方法來實現限制某些執行緒的執行許可權。
方便監控和除錯:透過 ThreadGroup 可以方便地檢視和操縱整個執行緒組中的所有執行緒,從而便於進行監控和除錯。
例如透過下面的方法就可以獲取當前執行緒組執行的所有執行緒:
IntStream.range(1, 10).forEach(i -> {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
thread.start();
});
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
Thread[] threads = new Thread[threadGroup.activeCount()];
threadGroup.enumerate(threads);
結果:
4. Java執行緒的狀態
在現在的作業系統中,執行緒是被視為輕量級程式的,所以作業系統執行緒的狀態其實和作業系統程式的狀態比較相似的。
4.1 Java執行緒的6個狀態
// Thread.State 原始碼
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
4.1.1 NEW
文件中含義為: Thread state for a thread which has not yet started.
表示尚未啟動的執行緒
Thread thread = new Thread(() -> {});
System.out.println(thread.getState()); // 輸出 NEW
現在有兩個問題:
- 反覆呼叫同一個執行緒的start()方法是否可行?
- 假如一個執行緒執行完畢(此時處於TERMINATED狀態),再次呼叫這個執行緒的start()方法是否可行?
從原始碼分析:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
從start0()看不出任何對state的操作。 我們只能從threadStatus 入手
可以看到 threadStatus 的變數。如果它不等於0,呼叫start()會直接丟擲異常的。
下面用程式碼測試一下:
@Test
public void testStartMethod() {
Thread thread = new Thread(() -> {}, "myThread");
thread.start(); // 第一次呼叫
thread.start(); // 第二次呼叫
}
第一次:
第二次:
而前面說到:如果它不等於0,呼叫start()會直接丟擲異常的。故答案是否,不能start兩次
那第二個問題: 假如一個執行緒執行完畢(此時處於 TERMINATED 狀態),再次呼叫這個執行緒的start()方法是否可行?
從獲取執行緒狀態原始碼分析:
// 返回執行緒的狀態
public State getState() {
// get current thread state
return sun.misc.VM.toThreadState(threadStatus);
}
public static Thread.State toThreadState(int var0) {
if ((var0 & 4) != 0) {
return State.RUNNABLE;
} else if ((var0 & 1024) != 0) {
return State.BLOCKED;
} else if ((var0 & 16) != 0) {
return State.WAITING;
} else if ((var0 & 32) != 0) {
return State.TIMED_WAITING;
} else if ((var0 & 2) != 0) {
return State.TERMINATED;
} else {
return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
}
}
獲取執行緒的狀態是透過 toThreadState(int var0)
函式獲取的, 引數就是 threadStatus
透過 var0
和 數字按位運算得到結果。
如果 (var0 & 4) != 0
成立,表示 var0
的第 3 位(從右往左數,從 0 開始計數)是 1,即表示執行緒狀態為 RUNNABLE;如果 (var0 & 1024) != 0
成立,表示 var0
的第 11 位是 1,即表示執行緒狀態為 BLOCKED。
回到剛才的問題,當執行緒處於 TERMINATED 狀態, 說明 var0
的第3位為0, 即 threadStatus ! = 0;
從之前的程式碼就可以看出會丟擲 IllegalThreadStateException 異常。故答案也是否
4.1.2 RUNNABLE
Java執行緒的RUNNABLE狀態其實是包括了傳統作業系統執行緒的ready和running兩個狀態。
4.1.3 BLOCKED
阻塞狀態。處於BLOCKED狀態的執行緒正等待鎖的釋放以進入同步區。
可以類比為一個人正在使用一部電話,而另一個人也想要使用電話。由於電話只有一個,因此另一個人必須等待當前使用者結束後才能使用。在此期間,他就處於blocked狀態。因為他知道那部電話已經被佔用了,不能立即使用。
4.1.4 WAITING
等待狀態。處於等待狀態的執行緒變成RUNNABLE狀態需要其他執行緒喚醒。
呼叫如下3個方法會使執行緒進入等待狀態:
- Object.wait():使當前執行緒處於等待狀態直到另一個執行緒喚醒它;
- Thread.join():等待執行緒執行完畢,底層呼叫的是Object例項的wait方法;
- LockSupport.park():除非獲得呼叫許可,否則禁用當前執行緒進行執行緒排程。
4.1.5 TIMED_WAITING
超時等待狀態。執行緒等待一個具體的時間,時間到後會被自動喚醒。自動喚醒後,擁有了去爭奪鎖的資格。
呼叫如下方法會使執行緒進入超時等待狀態:
- Thread.sleep(long millis):使當前執行緒睡眠指定時間;
- Object.wait(long timeout):執行緒休眠指定時間,等待期間可以透過notify()/notifyAll()喚醒;
- Thread.join(long millis):等待當前執行緒最多執行millis毫秒,如果millis為0,則會一直執行;
- LockSupport.parkNanos(long nanos): 除非獲得呼叫許可,否則禁用當前執行緒進行執行緒排程指定時間;
- LockSupport.parkUntil(long deadline):同上,也是禁止執行緒進行排程指定時間;
4.1.6 TERMINATED
終止狀態。此時執行緒已執行完畢。