切換語言為:簡體
Java 分析 concurrent 包下三種建立和管理執行緒的方法

Java 分析 concurrent 包下三種建立和管理執行緒的方法

  • 爱糖宝
  • 2024-07-29
  • 2061
  • 0
  • 0

引言

學習java.util.concurrent包下的類,建議按照以下順序進行,這樣可以逐步理解併發程式設計的基本概念和高階特性:

Java 分析 concurrent 包下三種建立和管理執行緒的方法

  1. 基礎執行緒機制:

    • Thread: 雖然不在java.util.concurrent包中,但理解基本的Thread類是開始學習併發程式設計的基礎。

    • RunnableCallable: 瞭解如何定義可以線上程中執行的任務。

  2. 基本的任務執行框架:

    • ExecutorExecutors: 學習如何使用這些介面和類來管理執行緒的建立和任務的提交。

    • ExecutorService: 深入瞭解如何管理任務的生命週期,包括關閉執行緒池。

  3. 同步工具類:

    • Future: 理解如何表示非同步計算的結果,並等待執行完成。

    • Semaphore, CountDownLatch, CyclicBarrier, Exchanger, Phaser: 學習這些同步輔助類,瞭解它們如何協調多個執行緒間的合作。

  4. 併發集合:

    • ConcurrentHashMap: 學習如何使用執行緒安全的Map實現。

    • ConcurrentLinkedQueue, CopyOnWriteArrayList: 瞭解不同的執行緒安全集合及其適用場景。

  5. 鎖機制:

    • LockReentrantLock: 學習如何使用顯式鎖來控制同步。

    • ReadWriteLockReentrantReadWriteLock: 理解如何使用讀寫鎖來提高併發讀取效能。

  6. 原子變數:

    • AtomicInteger, AtomicLong, AtomicReference等: 掌握如何使用原子變數進行無鎖程式設計。

  7. 高階併發工具:

    • ThreadPoolExecutorScheduledThreadPoolExecutor: 深入理解執行緒池的工作原理和如何自定義執行緒池的行為。

    • CompletionService: 學習如何管理非同步任務的執行和結果收集。

  8. Fork/Join框架:

    • ForkJoinPoolRecursiveTask: 學習分而治之的併發策略,以及如何利用這個框架來提高平行計算的效率。

概述

併發程式設計是Java開發中不可或缺的一部分,它允許開發者編寫能夠充分利用多核處理器效能的應用程式。本文從Java併發程式設計的基石——Thread類、Runnable介面以及Callable介面入手,詳細解釋瞭如何使用它們來建立和管理執行緒。討論瞭如何正確啟動和停止執行緒,如何等待執行緒的完成,以及為什麼不能重啟一個已經執行結束的執行緒。

正文

在現代軟件開發中,利用多核處理器的能力透過併發程式設計提高應用效能已經成為一項必備技能。Java作為一門歷史悠久的程式語言,提供了一套豐富的併發程式設計工具,其中Thread類、Runnable介面和Callable介面是最基礎的元件。本文將深入理解這些元件的使用方法和最佳實踐。

學習基礎執行緒機制時,理解Thread類、Runnable介面和Callable介面的工作原理是至關重要的。這些構建塊為在Java中進行併發程式設計提供了基礎。

Thread 類

Thread類代表了一個執行緒的例項。在Java中,執行緒是程式中的一個獨立執行路徑。每個執行緒都有自己的程式計數器、棧和區域性變數,但可以訪問共享的記憶體空間和物件。

當建立了Thread類的一個例項並呼叫它的start()方法時,JVM會為這個新執行緒分配資源,並呼叫它的run()方法來執行指定的程式碼。

建立和啟動執行緒

package com.dereksmart.crawling.core;
/**
 * @Author code_up
 * @Date 2024/7/29 8:55
 * @Description Thread測試類
 */
public class MyThread extends Thread {
    public void run() {
        // Code that executes on the new thread
        System.out.println("Hello,Derek Smart");
    }
}

 class TMain {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // This will call MyThread's run() method on a new thread
    }
}

停止執行緒

class MyThread1 extends Thread {
    public void run() {
        int i = 0;
        while (!interrupted()) {
            // 執行任務
            System.out.println("Hello,Derek Smart1");
        }
    }
}

 MyThread1 thread1 = new MyThread1();
        thread1.start(); // This will call MyThread's run() method on a new thread
        Thread.sleep(1);
        thread1.interrupt(); // 請求中斷

Java 分析 concurrent 包下三種建立和管理執行緒的方法

等待執行緒完成

myThread.join(); // 在當前執行緒中等待myThread執行緒完成

Runnable 介面

Runnable介面應該由任何類實現,如果例項打算透過某個執行緒執行。它是一個函式式介面,定義了一個無引數的方法run()

實現Runnable介面允許類不必繼承Thread類就能在新執行緒中執行。可以建立一個實現了Runnable介面的例項,並將它作為引數傳遞給Thread類的構造器。

建立和啟動執行緒

package com.dereksmart.crawling.core;
/**
 * @Author code_up
 * @Date 2024/7/29 8:55
 * @Description Runnable測試類
 */
public class MyRunnable implements Runnable {
    public void run() {
        // Code that executes on the new thread
        System.out.println("Hello,Derek Smart");
    }
}

 class RMain {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start(); // This will call MyRunnable's run() method on a new thread
    }
}

Callable 介面

Callable介面是一個泛型介面,定義了一個返回值的call()方法,並且可以丟擲異常。Callable介面通常用於那些需要返回結果的場景。

Runnable不同,Callablecall()方法可以返回一個值,並且可以丟擲一個異常。Callable任務需要提交給ExecutorService,它在執行後返回一個Future物件,透過這個Future物件可以獲取Callable的返回值。

建立和啟動執行緒

package com.dereksmart.crawling.core;

import java.util.concurrent.*;
/**
 * @Author code_up
 * @Date 2024/7/29 8:55
 * @Description Callable測試類
 */
public  class MyCallable implements Callable<String> {
    public String call() throws Exception {
        return "Callable completed";
    }
}

 class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Future<String> future = executorService.submit(new MyCallable());

        // 等待執行完成並獲取結果
        String result = future.get(); // 阻塞直到任務完成

        try {
            future.get(1000,TimeUnit.MINUTES);
        }catch (InterruptedException e){

        }catch (ExecutionException e){

        }catch (TimeoutException e){

        }

        future.cancel(true);
        System.out.println(result);
        future.cancel(true);
        // 關閉執行緒池
       // executorService.shutdown();
    }
}

停止執行緒

Callable任務一旦開始執行,就不能直接中斷。但是,可以透過Future呼叫cancel(true)方法來嘗試取消它,如果任務正在執行,會嘗試中斷它。

future.cancel(true); // 嘗試取消正在執行的任務

等待執行緒完成

使用Future.get()方法等待Callable任務完成。如果任務已經完成,這個方法會立即返回結果;否則,它會阻塞直到任務完成。

String result = future.get(); // 阻塞直到任務完成

等待執行緒完成 超時機制

使用Future.get()方法等待Callable任務完成。如果任務已經完成,這個方法會立即返回結果;否則,它會阻塞直到任務完成。

try {
    future.get(1000,TimeUnit.MINUTES); //1秒超時報錯
}catch (InterruptedException e){

}catch (ExecutionException e){

}catch (TimeoutException e){

}

重啟執行緒

在Java中,執行緒一旦完成執行就不能重新啟動。如果需要再次執行任務,需要建立一個新的執行緒例項。

其他核心方法

  • Thread.sleep(long millis): 當前執行緒暫停指定的毫秒數。

  • Thread.yield(): 提示執行緒排程器當前執行緒願意放棄其當前使用的處理器。這是對執行緒排程器的一個提示,排程器可能會忽略這個提示。

  • Thread.currentThread(): 返回當前正在執行的執行緒物件的引用。

  • Thread.setDaemon(boolean on): 將執行緒標記為守護執行緒或使用者執行緒。守護執行緒是指在後臺為其他執行緒提供服務的執行緒,如垃圾回收執行緒。

請注意,Thread.stop()方法已經被廢棄,因為它是不安全的。正確的停止執行緒的方式是使用中斷或者設定一個標誌變數。

原理

  • Thread: 每個Thread物件代表一個執行執行緒。start()方法會告訴JVM啟動一個新執行緒,這個新執行緒會呼叫run()方法。Thread類提供了管理執行緒生命週期的方法,比如interrupt(), join(), sleep(), yield()等。

  • Runnable: Runnable介面允許將任務的定義與執行分離。它沒有返回值,也不能丟擲檢查型異常。它可以用於建立可以執行在Thread上的任務。

  • Callable: Callable介面與Runnable類似,但它可以返回一個結果,並且可以丟擲檢查型異常。Callable任務通常用於那些需要返回值的場景,並且需要提交給ExecutorService來執行。

在Java中,建立執行緒的兩種常見方式是使用Thread類和實現Runnable介面。下面是各自的優缺點:

使用 Thread 類優點:

  • 簡單直觀:繼承Thread類並重寫run方法的方式簡單直觀,對於新手來說容易理解。

  • 直接控制執行緒:由於Thread類本身控制執行緒的執行,因此可以直接呼叫start, interrupt等方法來管理執行緒。

缺點:

  • 不支援多重繼承:在Java中,繼承了Thread類就不能再繼承其他類,這限制了類的靈活性。

  • 資源消耗較大:每個執行緒都是一個重量級的物件,涉及與作業系統的互動。

  • 擴充套件性差:如果需要將執行緒的執行邏輯與Thread類的管理分開,或者需要將執行緒邏輯與執行緒池等併發工具結合使用,直接使用Thread類就不那麼方便了。

實現 Runnable 介面

優點:

  • 分離任務和執行Runnable介面只代表一個要執行的任務,它不控制執行緒的生命週期。這使得任務程式碼可以被多個執行者(如執行緒或執行緒池)重用。

  • 更好的資源共享:實現Runnable的類更容易共享資源。多個執行緒可以接收同一個Runnable例項,並且可以訪問相同的資源,無需建立多個副本。

  • 更高的擴充套件性:由於Runnable是一個介面,你的類可以實現Runnable同時還可以繼承其他類,提供了更好的擴充套件性。

  • 適用於併發工具Runnable介面與java.util.concurrent包中的併發工具相容,可以與ExecutorService等高階併發工具一起使用,方便管理執行緒生命週期和任務執行。

缺點:

  • 無法直接控制執行緒:因為Runnable只是任務的抽象,它本身不提供對執行緒的直接控制,如中斷或獲取執行緒狀態等操作。這些需要在Thread例項上進行。

  • 需要額外的步驟建立執行緒:實現Runnable後,還需要建立一個Thread例項並將Runnable傳遞給它,然後才能啟動執行緒。

實現 Callable 介面

Callable是類似於Runnable的介面,但它允許任務返回值,並且可以丟擲異常。

優點:

  • 有返回值Callable可以返回執行結果,這是Runnable無法提供的。

  • 能丟擲異常:與Runnable不同,Callable中的call()方法允許丟擲異常,使得錯誤處理更加靈活。

缺點:

  • 需要配合Future使用:爲了獲取Callable任務的返回值,通常需要使用Future物件,這增加了程式設計的複雜性。

  • 不直接與Thread關聯:和Runnable一樣,Callable任務需要提交給ExecutorService,由執行緒池管理執行。

總的來說,實現RunnableCallable介面通常比繼承Thread類更靈活,特別是在使用執行緒池和執行器框架的現代併發程式設計中。然而,直接使用Thread類在某些簡單的情況下可能更方便,尤其是當需要直接管理執行緒的生命週期時。

結論

透過本文的學習,瞭解了Java併發程式設計的基礎,並掌握瞭如何使用ThreadRunnableCallable來建立併發程式。同時,也認識到了現代併發工具的重要性,並學會了如何將它們應用於實際開發中,以實現更高效、可靠的併發解決方案。隨著對這些工具的深入理解,將能夠編寫出更加健壯和高效能的Java應用程式。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.