1. 程式人生 > 其它 >Java 多執行緒學習筆記 01-程序(Process)與執行緒(Thread)

Java 多執行緒學習筆記 01-程序(Process)與執行緒(Thread)

Java 多執行緒學習筆記 01-程序(Process)與執行緒(Thread)

一、基本概念

程序

程式(Program)就是指令和資料的集合,而程序(Process)就是一個程式執行的過程,是系統分配資源的一個單位。
Windows 工作管理員開啟後,能看到的一個個的條目就是程序。

執行緒

而每個程序可以有很多個執行緒(Thread),執行緒是 CPU 排程和執行的單位。比如 QQ 可以同時發訊息和接收訊息,這裡就分別有傳送訊息和接受訊息的兩個執行緒。CPU 在每個執行緒之間隨機選擇執行,保證在一段時間內,每個執行緒都能按照規定和要求完成。

多執行緒

多執行緒讓 CPU 能同時處理多個任務。

很多多執行緒都是模擬出來的,真正的多執行緒是指有多個 CPU,即多核,比如伺服器。如果是模擬出來的多執行緒,比如單核 CPU,CPU 在同一個時間點只能執行一個程式碼,此時 CPU 在每一個執行緒之間反覆橫跳執行,因為切換的非常快,巨集觀層面上來看,聊 QQ 和聽音樂看起來是同時執行的,但微觀層面來看,每個時刻只有一個執行緒在執行。

程序和執行緒的區別

程序單獨佔有⼀定的記憶體地址空間,所以程序間存在記憶體隔離,資料是分開的,資料共享複雜但是同步簡單,各個程序之間互不⼲擾;⽽執行緒共享所屬程序佔有的記憶體地址空間和資源,資料共享簡單,但是同步複雜。可靠性較低。

程序的建立和銷燬不僅需要儲存暫存器和棧資訊,還需要資源的分配回收以及⻚排程,開銷較⼤;執行緒只需要儲存暫存器和棧資訊,開銷較⼩。

概念總結

  • 執行緒就是獨立的執行路徑。
  • 程式執行時,即使沒有自己建立執行緒,那麼至少也會有一個 main 執行緒作為系統的入口,用於執行整個程式。
  • 在一個程序中,如果開闢了多個執行緒,執行緒的運由排程器安排排程,先後順序由系統決定,無法人為直接干預。
  • 對同一份資源操作時(比如印表機),存在資源搶奪問題,需要加入併發控制。
  • 執行緒切換有額外的開銷。
  • 每個執行緒在自己的工作記憶體中互動,記憶體控制不得當會造成資料不一致。

二、Java 中的執行緒

  1. main 執行緒
  2. 使用者開闢的執行緒
  3. GC 執行緒
  4. 其他執行緒

三、建立 Java 執行緒的幾種方式

繼承 Thread 類

不建議使用,Java 中的單繼承會造成侷限性。

  1. 編寫自定義類繼承 Thread 類
public class MyThread extends Thread {
}
  1. 重寫父類的 run()方法
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("MyThread" + i);
        }
    }
}
  1. 建立自定義類的例項,並呼叫 start()方法
    public static void main(String[] args) {
        MyThread myth = new MyThread();
        myth.start();

        for (int i = 0; i < 50; i++) {
            System.out.println("MainThread" + i);
        }
    }
  1. 檢視輸出結果
MyThread0
MyThread1
MyThread2
MyThread3
MyThread4
MyThread5
MyThread6
MyThread7
MyThread8
MyThread9
MyThread10
MyThread11
MyThread12
MainThread0
MainThread1
MainThread2
MainThread3
MainThread4
MyThread13
MyThread14
...

程序已結束,退出程式碼為 0

實現 Runnable 介面

建議使用,避免單繼承造成的侷限性,使用靈活,方便同一個物件被多個執行緒使用。

原理:通過 Thread 物件代理執行。

  1. 建立自定義類並實現 Runnable 介面
public class MyRunnableImpl implements Runnable{
}
  1. 重寫 run()方法
public class MyRunnableImpl implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("MyThread" + i);
        }
    }
}
  1. 建立自定義類的例項,建立 Thread 類的例項並傳入自定義類例項,呼叫 Thread 類例項 start()方法
    public static void main(String[] args) {
        MyRunnableImpl runnable = new MyRunnableImpl();
        Thread th = new Thread(runnable);
        th.start();

        for (int i = 0; i < 50; i++) {
            System.out.println("MainThread" + i);
        }
    }
  1. 檢視輸出結果
MyThread0
MyThread1
MyThread2
MyThread3
MyThread4
MyThread5
MyThread6
MyThread7
MyThread8
MyThread9
MyThread10
MyThread11
MyThread12
MainThread0
MainThread1
MainThread2
MainThread3
MainThread4
MyThread13
MyThread14
...

程序已結束,退出程式碼為 0

實現 Callable 介面

原理和上一個方法類似。

  1. 編寫自定義類實現 Callable 介面,需要返回值型別,重寫 call 方法
public class MyCallableImpl implements Callable<String> {
    @Override
    public String call() throws Exception {
        DateFormat format = DateFormat.getDateInstance();
        // 獲取當前執行緒名
        String currName = Thread.currentThread().getName();
        return currName + ":" + format.format(new Date());
    }
}
  1. 建立物件
MyCallableImpl myCallable1 = new MyCallableImpl();
MyCallableImpl myCallable2 = new MyCallableImpl();
MyCallableImpl myCallable3 = new MyCallableImpl();
  1. 建立執行服務
ExecutorService service = Executors.newFixedThreadPool(3);
  1. 提交執行
Future<String> task1 = service.submit(myCallable1);
Future<String> task2 = service.submit(myCallable2);
Future<String> task3 = service.submit(myCallable3);
  1. 獲取結果
try {
    String str1 = task1.get();
    String str2 = task2.get();
    String str3 = task3.get();
    System.out.println(str1);
    System.out.println(str2);
    System.out.println(str3);
} catch (Exception e) {
    e.printStackTrace();
}
  1. 關閉服務
service.shutdown();
  1. 檢視執行結果
pool-1-thread-1:2021年12月6日
pool-1-thread-2:2021年12月6日
pool-1-thread-3:2021年12月6日

程序已結束,退出程式碼為 0