天天看點

線程池使用

線程池

線程池是一種常用的并發程式設計技術,它可以在程式啟動時建立一定數量的線程,并且将這些線程儲存在一個池子中,等待來自任務隊列的任務。當一個任務到來時,線程池中的一個空閑線程會被配置設定給該任務,執行完任務後,該線程又會傳回到線程池中,等待下一個任務的到來。通過複用線程,線程池可以避免線程的頻繁建立和銷毀,提高程式的性能和效率。

Executors

Java中的Executors類提供了一些簡單的工廠方法,用于建立常見的線程池。這些工廠方法傳回的線程池都是ThreadPoolExecutor的執行個體,可以根據需要進行自定義配置。以下是一些常用的工廠方法:

1. newFixedThreadPool(int nThreads):建立一個固定大小的線程池,池子中有nThreads個線程。

2. newCachedThreadPool():建立一個可緩存的線程池,池子中的線程數可以根據需要自動增加或減少。

3. newSingleThreadExecutor():建立一個隻有一個線程的線程池,所有任務都在同一個線程中按順序執行。

4. newScheduledThreadPool(int corePoolSize):建立一個固定大小的線程池,用于執行定時任務和周期性任務。

Executors類還提供了一些其他的工廠方法,比如newWorkStealingPool、newSingleThreadScheduledExecutor等,可以根據具體需求進行選擇。

需要注意的是,雖然Executors類提供了一些便捷的工廠方法,但是在實際應用中,我們仍然需要根據具體情況進行适當的配置和調整,以保證線程池的性能和穩定性。特别是在建立可緩存線程池時,需要注意控制線程數,避免線程數量過多導緻系統資源耗盡。

ThreadFactory詳解

ThreadFactory是Java中的一個接口,用于建立新的線程。它定義了一個方法newThread(Runnable r),用于建立并傳回一個新的Thread對象,傳入的Runnable對象則作為線程的執行體。ThreadFactory通常用于自定義線程池中的線程建立過程,可以通過它來設定線程的名稱、優先級、是否為守護線程等。

ThreadFactory接口隻有一個抽象方法newThread(Runnable r),是以可以使用Lambda表達式或方法引用來實作。例如,可以使用匿名内部類來實作ThreadFactory接口:

ThreadFactory threadFactory = new ThreadFactory() {
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setName("MyThread-" + t.getId());
        t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
};           

上述代碼建立了一個自定義的ThreadFactory對象,通過重寫newThread方法來建立新的線程。在這個例子中,建立的線程會被命名為"MyThread-"加上線程的唯一辨別符,設定為普通優先級。這個ThreadFactory對象可以被用于建立線程池中的線程。

在使用線程池時,很多情況下需要自定義ThreadFactory來建立線程。這樣可以更好地掌控線程的建立過程,進而更好地控制線程的性能和行為。

總之,ThreadFactory是一個用于建立新線程的接口,通過實作它的newThread方法來自定義線程的建立過程。它通常用于自定義線程池中的線程建立過程,可以通過它來設定線程的名稱、優先級、是否為守護線程等。

ThreadPoolExecutor源碼分析

ThreadPoolExecutor是Java中實作線程池的一個類,它提供了靈活的線程池實作機制,可以通過配置不同的參數來調整線程池的大小、任務隊列和拒絕政策等。下面對ThreadPoolExecutor的源碼進行分析。

  1. 構造函數

ThreadPoolExecutor有多個構造函數,其中最常用的是以下構造函數:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
                           TimeUnit unit, BlockingQueue<Runnable> workQueue) { ... }           

該構造函數接收5個參數,分别是:

  • corePoolSize:線程池中的核心線程數,即線程池中始終存在的線程數。
  • maximumPoolSize:線程池中允許的最大線程數。
  • keepAliveTime:非核心線程的閑置時間,超過該時間就會被回收。
  • unit:keepAliveTime的時間機關。
  • workQueue:任務隊列,用于存放等待執行的任務。
  1. 線程池狀态

ThreadPoolExecutor定義了一個内部枚舉類,用于表示線程池的狀态,包括RUNNING、SHUTDOWN、STOP、TIDYING和TERMINATED。線程池的狀态可以通過一個volatile字段ctl來表示,該字段使用高3位表示線程池的狀态,低29位表示工作線程的數量。

  1. 送出任務

ThreadPoolExecutor的submit方法用于送出一個任務,它會将任務包裝成一個FutureTask對象,并将其加入到任務隊列中。如果目前線程池中的線程數小于corePoolSize,則會建立一個新的工作線程來執行任務,否則會将任務加入到任務隊列中等待執行。如果任務隊列已滿,且目前線程池中的線程數小于maximumPoolSize,則會建立新的工作線程來執行任務。如果線程池已經關閉,則會拒絕任務。

  1. 執行任務

線程池中的工作線程會不斷從任務隊列中取出任務并執行,直到線程池關閉或者發生異常。ThreadPoolExecutor使用一個内部類Worker來表示工作線程,它實作了Runnable接口,并包含了一個Thread對象,用于執行任務。Worker在執行任務時會調用runWorker方法,該方法會循環從任務隊列中擷取任務并執行,直到線程池關閉或者發生異常。

  1. 線程池關閉

當線程池調用shutdown方法時,線程池的狀态會變為SHUTDOWN,此時線程池會停止接收新的任務,并等待已送出的任務執行完畢。當線程池調用shutdownNow方法時,線程池的狀态會變為STOP,此時線程池會嘗試停止所有正在執行的任務,并傳回未執行的任務。當線程池中的所有任務都執行完畢後,線程池的狀态會變為TERMINATED。

  1. 拒絕政策

當任務隊列已滿并且線程池中的線程數已經達到maximumPoolSize時,ThreadPoolExecutor會拒絕任務。ThreadPoolExecutor提供了4種拒絕政策,分别是AbortPolicy、CallerRunsPolicy、DiscardPolicy和DiscardOldestPolicy。其中AbortPolicy是預設的拒絕政策,它會抛出RejectedExecutionException異常。

以上是ThreadPoolExecutor的源碼分析,通過分析ThreadPoolExecutor的源碼,我們可以深入了解線程池的實作機制,并且可以根據實際需求來靈活地配置線程池的參數和拒絕政策。

線程池的實作原理

可以使用一個線程來實作很多任務的處理,具體實作方式可以通過以下兩種方式實作:

  1. 任務隊列:可以将需要執行的任務放入一個任務隊列中,然後由一個線程循環從任務隊列中取出任務進行處理。這種方式可以減少線程的建立和銷毀,提高線程的複用率和系統的性能。例如,下面是一個簡單的示例代碼,示範了如何使用任務隊列來實作多任務處理:
// 建立任務隊列
LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();

// 建立處理任務的線程
Thread thread = new Thread(() -> {
      while (true) {
        // 從任務隊列中取出一個任務進行處理
        Runnable task = null;
        try {
          task = taskQueue.take();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        task.run();
      }
 });

// 啟動線程
thread.start();

// 送出任務
taskQueue.add(() -> {
    // 第一個任務的處理邏輯
});
taskQueue.add(() -> {
    // 第二個任務的處理邏輯
});           

在上述代碼中,建立了一個任務隊列taskQueue,并使用一個線程來處理任務。線程會不斷地從任務隊列中取出任務進行處理,直到任務隊列為空。然後,通過向任務隊列中添加任務的方式來送出任務。

  1. 線程池:可以使用線程池來管理和排程多個任務的執行,将多個任務配置設定給線程池中的多個線程來并發執行。線程池可以充分利用系統資源,提高系統的性能和并發能力。例如,下面是一個簡單的示例代碼,示範了如何使用線程池來實作多任務處理:
// 建立線程池
ExecutorService executor = Executors.newFixedThreadPool(10);

// 送出任務
executor.submit(() -> {
    // 第一個任務的處理邏輯
});
executor.submit(() -> {
    // 第二個任務的處理邏輯
});

// 關閉線程池
executor.shutdown();           

在上述代碼中,建立了一個具有10個線程的線程池,然後通過executor.submit方法來送出任務,線程池會自動排程可用的線程來執行任務。最後,通過executor.shutdown方法來關閉線程池。

需要注意的是,無論是使用任務隊列還是線程池來實作多任務處理,都需要考慮線程安全和并發通路的問題,以避免出現線程安全問題。同時,還需要根據具體的業務場景和性能需求來選擇适合的實作方式。

線程池狀态對應的值

線程池中的 ctl 變量是一個原子整數,用于存儲線程池的狀态和線程數量資訊。具體來說,線程池的狀态資訊存儲在 ctl 變量的高 3 位,線程數量資訊存儲在 ctl 變量的低 29 位。

線程池對應的值如下:

- RUNNING:表示線程池正在運作中,此時 ctl 變量的高 3 位為 111(即 -1 << 29),低 29 位表示線程數量資訊。

- SHUTDOWN:表示線程池已經關閉,不再接受新的任務,但是會執行已經送出的任務,此時 ctl 變量的高 3 位為 000(即 0),低 29 位表示線程數量資訊。

- STOP:表示線程池已經關閉,不再接受新的任務,也不會執行已經送出的任務,此時 ctl 變量的高 3 位為 001(即 1 << 29),低 29 位表示線程數量資訊。

- TIDYING:表示線程池正在關閉中,所有任務已經執行完畢,但是還有一些線程正在執行清理操作,此時 ctl 變量的高 3 位為 010(即 2 << 29),低 29 位表示線程數量資訊。

- TERMINATED:表示線程池已經徹底關閉,所有任務已經執行完畢,所有線程也已經終止,此時 ctl 變量的高 3 位為 011(即 3 << 29),低 29 位表示線程數量資訊。

Executor 和 Executors 的差別

Executor 是一個接口,而 Executors 是一個工具類。

Executor 接口定義了一種用于執行任務的标準接口,它隻包含一個方法 execute(Runnable command),用于執行一個 Runnable 類型的任務。Executors 則是一個工具類,它提供了一些靜态方法來建立不同類型的 Executor 對象。

ExecutorService 是 Executor 接口的子接口,它們之間的關系是 ExecutorService 繼承了 Executor 接口,并且在 Executor 接口的基礎上提供了更豐富的線程管理功能。

線程池中 submit() 和 execute() 方法的差別

線程池中的 submit() 和 execute() 方法都可以用來送出任務給線程池執行,它們的主要差別如下:

1. 傳回值不同:

submit() 方法會傳回一個 Future 對象,可以用來擷取任務執行的結果或取消任務的執行。而 execute() 方法沒有傳回值。

2. 抛出異常不同:

submit() 方法會将任務執行過程中抛出的異常封裝在 Future 對象中,可以在調用 Future.get() 方法時擷取到異常并處理。而 execute() 方法會将異常直接抛到調用者線程中,需要在任務代碼中捕獲并處理。

3. 任務類型不同:

submit() 方法可以送出 Runnable 類型的任務和 Callable 類型的任務,而 execute() 方法隻能送出 Runnable 類型的任務。

4. 執行方式不同:

submit() 方法可以異步執行任務并傳回 Future 對象,可以使用 Future.get() 方法阻塞等待任務執行完成。而 execute() 方法是同步執行任務,任務完成後才會傳回,不能使用 Future.get() 方法阻塞等待任務執行完成。

總的來說,submit() 方法可以送出 Callable 類型的任務,并且可以擷取任務執行的結果或取消任務的執行,适用于需要異步執行任務并擷取結果的場景。而 execute() 方法隻能送出 Runnable 類型的任務,并且是同步執行任務,适用于不需要擷取任務執行結果的場景。在實際應用中,我們需要根據具體的需求和場景來選擇适合的方法來送出任務給線程池執行。

線程組,為什麼在 Java 中不推薦使用

Java 中的線程組(ThreadGroup)是一種用于管理線程的機制,它可以将多個線程歸為一組,并且可以對整個線程組進行操作,例如設定線程組的優先級、中斷線程組中的所有線程等。但是在 Java 中,并不推薦使用線程組,主要有以下幾個原因:

1. 線程組的層次結構不夠靈活:線程組的層次結構是樹形結構,但是在實際應用中,往往需要更加靈活的線程組結構。例如,如果需要将線程分組并且允許一個線程同時屬于多個線程組,那麼線程組就無法滿足這種需求。

2. 線程組的功能受限:線程組提供的功能相對較少,隻能設定線程組的優先級、中斷線程組中的所有線程等,而不能實作更加複雜的功能,例如限制線程組中的線程數量、設定線程組的執行順序等。

3. 線程組的性能問題:線程組在建立和管理線程時會帶來一定的性能開銷,尤其是在大規模應用中使用線程組可能會導緻嚴重的性能問題。

4. 線程組的安全問題:線程組的安全性問題也比較突出,如果線程組沒有被正确地管理和使用,可能會導緻線程安全問題的發生。

總的來說,雖然線程組提供了一種管理線程的機制,但是在實際應用中,由于線程組的層次結構不夠靈活、功能受限、性能問題和安全問題等方面的原因,Java 中并不推薦使用線程組,而是推薦使用更加靈活和功能強大的線程池來管理線程。