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

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(終止)

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

image

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();

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

image