夯實Java基礎,一篇文章全解析執行緒問題

2022-11-02 15:00:20

1. 執行緒是什麼

作業系統支援多個應用程式並行執行,每個應用程式至少對應一個程序 ,彼此之間的操作和資料不受干擾,彼此通訊一般採用管道通訊、訊息佇列、共用記憶體等方式。當一個程序需要磁碟IO的時候,CPU就切換到另外的程序,提高了CPU利用率。

有了程序,為什麼還要執行緒?因為程序的成本太高了。

啟動新的程序必須分配獨立的記憶體空間,建立資料表維護它的程式碼段、堆疊段和資料段,這是昂貴的多工工作方式。執行緒可以看作輕量化的程序。執行緒之間使用相同的地址空間,切換執行緒的時間遠小於切換程序的時間。

程序是資源分配的最小單位,而執行緒是CPU排程的最小單位。每一個程序中至少有一個執行緒,同一程序的所有執行緒共用該程序的所有資源,多個執行緒可以完成多個不同的任務,也就是我們常說的並行多執行緒。

2. 怎樣建立執行緒

建立執行緒常用的有四種方式,分別是:

  1. 繼承Thread類
  2. 實現Runnable介面
  3. 實現Callable介面
  4. 使用執行緒池建立

分別看一下怎麼具體怎麼使用程式碼建立的?

2.1 繼承Thread類

public class ThreadDemo {

    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start(); // 啟動執行緒
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("關注公眾號:一燈架構");
    }
}

輸出結果:

關注公眾號:一燈架構

start方法用來啟動執行緒,只能被呼叫一次。

run方法是執行緒的核心方法,業務邏輯都寫在run方法中。

2.2 實現Runnable介面

public class ThreadDemo {

    public static void main(String[] args) {
				MyRunnable myRunnable = new MyRunnable();
        Thread thread1 = new Thread(myRunnable, "執行緒1");
        Thread thread2 = new Thread(myRunnable, "執行緒2");
        thread1.start(); // 啟動執行緒1
        thread2.start(); // 啟動執行緒2
    }
}

class MyRunnable implements Runnable {
    private int count = 5;

    @Override
    public void run() {
        while (count > 0) {
            System.out.println(Thread.currentThread().getName()
                    + ",關注公眾號:一燈架構," + count--);
        }
    }
}

輸出結果:

執行緒2,關注公眾號:一燈架構,4
執行緒1,關注公眾號:一燈架構,5
執行緒1,關注公眾號:一燈架構,2
執行緒1,關注公眾號:一燈架構,1
執行緒2,關注公眾號:一燈架構,3

需要把Runnable範例放到Thread類中,才能執行,Thread物件才是真正的執行緒物件。

使用實現Runnable介面建立執行緒方式,相比繼承Thread類建立執行緒,優點是:

  1. 實現的方式沒有類的單繼承性的侷限性
  2. 實現的方式更適合來處理多個執行緒有共用資料的情況

2.3 實現Callable介面

public class ThreadTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        FutureTask<String> futureTask = new FutureTask<String>(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println(futureTask.get());
    }
}

class MyCallable implements Callable {
    @Override
    public String call() throws Exception {
        return "關注公眾號:一燈架構";
    }
}

輸出結果:

關注公眾號:一燈架構

實現Callable介面的執行緒範例物件,配合FutureTask使用,可以接收返回值。

2.4 使用執行緒池建立

public class ThreadDemo {

    public static void main(String[] args)  {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService.execute(() -> System.out.println("關注公眾號:一燈架構"));
    }
}

輸出結果:

關注公眾號:一燈架構

使用執行緒池建立執行緒是工作開發中最常用的方式,優點是:

  1. 執行緒池幫忙管理物件的建立與銷燬,減輕開發者工作量
  2. 執行緒池幫忙管理任務的呼叫,資源的建立與分配
  3. 複用執行緒和物件,提高使用效率

3. 執行緒的狀態

執行緒共有6種狀態,分別是NEW(初始化)、RUNNABLE(可執行)、WAITING(等待)、TIMED_WAITING(超時等待)、BLOCKED(阻塞)、TERMINATED(終止)。

  • NEW(初始化)

    表示建立執行緒物件之後,還沒有呼叫start方法。

  • RUNNABLE(可執行)

    表示呼叫start方法之後,等待CPU排程。為了便於理解,通常又把RUNNABLE分別RUNNING(執行中)和READY(就緒)。處在RUNNING(執行中)狀態的執行緒可以呼叫yield方法,讓出CPU時間片,然後跟其他處於READY(就緒)一起等待被排程。

  • WAITING(等待)

    處於RUNNABLE狀態的執行緒呼叫wait方法之後,就處於等待狀態,需要其他執行緒顯示地喚醒。

  • TIMED_WAITING(超時等待)

    處於RUNNABLE狀態的執行緒呼叫wait(long)方法之後,就處於等待狀態,需要其他執行緒顯示地喚醒。

  • BLOCKED(阻塞)

    等待進入synchronized方法/程式碼塊,處於阻塞狀態。

  • TERMINATED(終止)

    表示執行緒已經執行結束。

4. 執行緒常用方法

說一下執行緒有哪些常用的方法。

方法定義 含義 使用方式
public synchronized void start() {……} 啟動執行緒 MyThread myThread = new MyThread();
myThread.start();
public static native Thread currentThread(); 獲取當前執行緒範例物件 Thread thread = Thread.currentThread();
public static native void yield(); 讓出CPU時間片 Thread.yield();
public static native void sleep(long millis); 睡眠指定時間 Thread.sleep(1L);
public void interrupt() {……} 中斷執行緒 MyThread myThread = new MyThread();
myThread.interrupt();
public static boolean interrupted() {……} 判斷執行緒是否已中斷 MyThread myThread = new MyThread();
boolean interrupted = myThread.isInterrupted();
public final native boolean isAlive(); 判斷執行緒是否是存活狀態 MyThread myThread = new MyThread();
boolean alive = myThread.isAlive();
public final String getName() {……} 獲取執行緒名稱 MyThread myThread = new MyThread();
String name = myThread.getName();
public State getState() {……} 獲取執行緒狀態 MyThread myThread = new MyThread();
Thread.State state = myThread.getState();
public long getId() {……} 獲取執行緒ID MyThread myThread = new MyThread();
long id = myThread.getId();
public final void join() {……} 等待其他執行緒執行完再執行 MyThread myThread = new MyThread();
myThread.join();

我是「一燈架構」,如果本文對你有幫助,歡迎各位小夥伴點贊、評論和關注,感謝各位老鐵,我們下期見