本文目錄:
進程和線程
什麼是進程和線程?
在計算機中,一個任務就是一個進程
在進程內部還需要多個子任務,每個子任務被稱爲線程。
一個進程可以包含一個或多個線程(至少一個)。
進程和線程的關係
- 一個進程可以包含一個或多個線程(至少一個線程)
- 線程是操作系統調度的最小任務單位
- 如何調度線程完全由操作系統決定
實現多任務的方法
- 多進程模式(每個進程只有一個線程)
- 多線程模式(一個進程有多個線程)
- 多進程+多線程(複雜度最高)
多進程 vs 多線程
- 創建進程比創建線程開銷大
- 進程間通信比線程間通信慢
- 多進程穩定性比多線程高
Java內置多線程支持
- 一個Java程序實際上是一個JVM進程
- JVM用一個主線程來執行main()方法
- 在main()方法中又可以啓動多個線程
多線程編程特點
- 多線程需要讀寫共享數據
- 多線程經常需要同步
- 多線程編程的複雜度高,調試更困難
Java多線程編程特點
- 多線程模型是Java程序最基本的併發模型
- 網絡、數據庫、Web等都依賴多線程模型
- 必須掌握Java多線程編程才能繼續深入學習
線程創建方式
Thread類
Java爲我們提供了一個Thread類來操作線程,基本使用如下
public class ThreadTest {
public static void main(String[] args) {
// 創建線程對象
Thread thread = new Thread();
// 啓動線程
thread.start();
}
}
線程啓動後虛擬機會自動調用run()方法(自己調用沒有任何意義),直接使用Thread類對象啓動線程run()方法不會執行任何操作,因此需要我們自己定義Thread的子類並重寫run()方法來執行我們自己的操作
class SubThread extends Thread {
@Override
public void run() {
System.out.println("自定義線程運行中。。。")
}
}
public class ThreadTest {
public static void main(String[] args) {
SubThread subThread = new SubThread();
subThread.start();
}
}
Runnable接口
另一種創建線程的方法是實現Runnable接口,並重寫run()方法
class MyThread implements Runnable {
@Override
public void run() {
System.out.println("MyThread running...");
}
}
但是創建線程對象和啓動線程的方法稍有不同
public class Main {
public static void main(String[] args) {
// 創建Runnable的實現類對象
Runnable runnable = new MyThread();
// 通過Runnable實現類對象創建Thread對象
Thread thread = new Thread(runnable);
// 啓動線程
thread.start();
}
}
總結
- Java用Thread對象表示一個線程,通過調用start()啓動一個線程
- 一個線程對象只能調用一次start()
- 線程的執行代碼是run()方法
- 線程調度由操作系統決定,程序本身無法決定
- Thread().sleep()可以把當前線程暫停一段時間
線程的狀態
線程狀態
- New(新創建)
- Runnable(運行中)
- Blocked(被阻塞)
- Waiting(等待)
- Timed Waiting(計時等待)
- Terminated(已終止)
線程終止原因
- run()方法執行到return語句返回(線程正常終止)
- 因爲未捕獲的異常導致線程終止(線程意外終止)
- 對某個線程的Thread實例調用stop()方法強制終止(不推薦)
線程等待
當A線程運行時調用了B線程的join()方法,則會等待B線程執行結束後繼續執行A線程。
public class Main {
public static void main(String[] args) {
SubThread subThread = new SubThread();
subThread.start();
try {
// 主線程會等待subthread執行完成
subThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主線程結束。。。");
}
}
也可以指定等待的時間,超過等待時間線程仍然沒有結束則不再等待,如果不指定或指定時間爲0則表示一直等待。
public class Main {
public static void main(String[] args) {
SubThread subThread = new SubThread();
subThread.start();
try {
// 等同於subThread.join(0);表示一直等待
subThread.join();
// 主線程最多等待subthread線程1000ms
subThread.join(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主線程結束。。。");
}
}
如果調用join()方法的線程已經運行結束了,則會立即返回不進行等待。
中斷線程
什麼是中斷線程
如果線程需要執行一個長時間任務,就可能需要能中斷線程。
class MyThread extends Thread {
@Override
public void run() {
// 長時間循環執行任務
while (true) {
// do something...
}
}
}
中斷線程就是其他線程給該線程發一個信號,該線程收到信號後結束執行run()方法。需要被中斷的線程就是中斷線程。
線程中斷方法
檢測isInterrupted()
中斷線程需要通過檢測isInterrrupted()標誌來決定要不要中斷,其他線程通過調用interrupt()方法中斷該線程。
class MyThread extends Thread {
@Override
public void run() {
// 檢測isInterrupted()標誌決定是否繼續執行
while (!isInterrupted()) {
System.out.println("running...");
}
}
}
puclic class Main {
public static void main(String[] args) throws Exception {
Thread t = new MyThread();
t.start();
Thread.sleep(1000);
// 中斷線程
t.interrupt();
}
}
如果線程處於等待狀態,該線程會捕獲InterruptedException,補貨到該異常說明其他線程對其調用了interrupt()方法,通常情況下該線程應該立刻結束運行。
class MyThread extends Thread {
public void run() {
while(!isInterrupted()) {
System.out.println("running...");
try {
// 線程處於等待狀態
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
// 捕獲到中斷異常,立即結束線程運行
return;
}
}
}
}
自定義running標誌檢測
通過檢測自定義running標誌位來代替檢測isInserrupted(),通過修改running的值來決定線程是否中斷。
class MyThread extends Thread {
// 自定義一個標識位
public volatile boolean ruuning = true;
public void run() {
// 檢測自定義標識位
while (running) {
// do something...
}
}
}
public class Main {
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
Thread.sleep(1000);
// 修改自定義標識位
t.running = false;
}
}
標識位使用volatile來標記,是因爲線程間共享變量需要使用volatile標記,以確保線程能讀取到更新後的變量值。
volatile簡介
Java內存模型
在Java虛擬機中,變量的值保存在主內存中,當線程訪問一個變量的時候,會先獲取一個副本,並且保存到自己的工作內存中,如果線程修改了變量的值,虛擬機會在某個時刻把值回寫到主內存,但這個時間是不固定的。
volatile作用
volatile關鍵字的目的是告訴虛擬機:
-
每次訪問變量是,總是獲取主內存的最新值
-
每次修改變量後,立刻回寫到主內存
因此volatile關鍵字解決的是可見性問題:
- 當一個線程修改了某個共享變量的值,其他線程能夠立刻看到修改後的值
總結
- 調用interrupt()方法可以中斷一個線程
- 通過檢測isinterrupted()標識獲取當前線程是否已中斷
- 如果線程處於等待狀態,該線程會捕獲InterruptedException
- isInterrupted()爲true或者捕獲了InterruptedException都應該立刻結束
- 通過標識位判斷需要正確使用volatile關鍵字
- volatile關鍵字解決了共享變量在線程間的可見性問題
守護線程
Java中所有線程結束後,JVM退出,如果有無限循環(定時任務)的線程,則虛擬機無法退出,這是就需要守護線程。
什麼是守護線程
- 守護線程是爲其他線程服務的線程
- 所有非守護線程都執行完畢後,虛擬機退出
特點
- 守護線程不能持有任何資源(如打開文件等),因爲虛擬機退出時守護線程無法自動釋放資源。
創建守護線程
設置線程屬性setDaemon(true)即可把該線程變爲守護線程。
class TimerThread extends Thread {
@Override
public void run() {
// 無限循環
while (true) {
System.out.println(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
}
}
public class Main {
public static void main(String[] args) throws Exception {
System.out.println("Main start");
TimerThread t = new TimerThread();
t.start();
Thread.sleep(5000);
System.out.println("Main end");
}
}
因爲子線程在無限循環地打印,所以當Main end輸出後程序並不會結束,子線程還在繼續打印時間,如果把子線程變爲守護線程則程序就正常退出了
//TimerThread類同上
public class Main {
public static void main(String[] args) throws Exception {
System.out.println("Main start");
TimerThread t = new TimerThread();
// 設爲守護線程,必須在線程啓動之前,否則會出現異常
t.setDaemon(true);
t.start();
Thread.sleep(5000);
System.out.println("Main end");
}
}
Java中的垃圾回收線程就是一個守護線程,它始終在低級別的狀態下運行,實時監控和管理系統中的可回收資源。當所有非守護線程都退出時,守護線程也就沒有存在的必要了,乖乖的自己離開。