Java並行流:一次搞定多執行緒程式設計難題,讓你的程式飛起來!

不一樣的科技宅發表於2023-04-20

前言

  在日常的工作中,為了提高程式的處理速度,充分利用多核處理器的效能,我們需要手動編寫多執行緒程式碼。但是多執行緒程式設計非常複雜,容易出現死鎖、競態條件等問題,給我們帶來了很大的困擾。而 Java 並行流則提供了一種更加簡單、易用、安全的併發程式設計方式,可以讓我們更加輕鬆地編寫高效的併發程式。

使用多執行緒下載檔案

public class MultiThreadExample {

    public static void main(String[] args) throws InterruptedException {

        List<String> urls = Arrays.asList(
            "https://example.com/file1.txt",
            "https://example.com/file2.txt",
            "https://example.com/file3.txt",
            "https://example.com/file4.txt",
            "https://example.com/file5.txt"
        );
        
        int threads = 5;
        int chunkSize = urls.size() / threads;
        int startIndex = 0;
        int endIndex = chunkSize;

        // 建立執行緒列表
        List<DownloadThread> downloadThreads = new ArrayList<>();

        // 啟動多個執行緒進行檔案下載
        for (int i = 0; i < threads; i++) {
            downloadThreads.add(new DownloadThread(urls, startIndex, endIndex));
            downloadThreads.get(i).start();
            startIndex += chunkSize;
            endIndex += chunkSize;
        }

        // 等待所有執行緒結束並彙總結果
        for (DownloadThread downloadThread : downloadThreads) {
            downloadThread.join();
        }

        System.out.println("檔案下載完成");
    }
}

class DownloadThread extends Thread {
    private List<String> urls;
    private int start;
    private int end;

    public DownloadThread(List<String> urls, int start, int end) {
        this.urls = urls;
        this.start = start;
        this.end = end;
    }

    @Override
    public void run() {
        for (int i = start; i < end; i++) {
            HttpUtil.download(urls.get(i));
        }
    }
}

  我們首先將要下載的檔案 URL 儲存在一個 List 中,然後為每個塊建立了一個 DownloadThread 物件,並啟動了多個執行緒進行下載操作。每個執行緒只負責處理 URL 的一個塊,呼叫 HttpUtil.download 方法進行檔案下載操作。最後,我們等待所有執行緒結束即可。

使用Fork/Join進行下載

  import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;

public class ForkJoinExample {

    public static void main(String[] args) {
        List<String> urls = Arrays.asList(
            "https://example.com/file1.txt", 
            "https://example.com/file2.txt", 
            "https://example.com/file3.txt", 
            "https://example.com/file4.txt", 
            "https://example.com/file5.txt"
        );
        ForkJoinPool pool = new ForkJoinPool();
        pool.invoke(new DownloadAction(urls, 0, urls.size()));
        System.out.println("檔案下載完成");
    }

    static class DownloadAction extends RecursiveAction {
        private List<String> urls;
        private int start;
        private int end;

        public DownloadAction(List<String> urls, int start, int end) {
            this.urls = urls;
            this.start = start;
            this.end = end;
        }

        @Override
        protected void compute() {
            if (end - start <= 1) {
                HttpUtil.download(urls.get(start));
                return;
            }

            int mid = (start + end) / 2;
            DownloadAction leftAction = new DownloadAction(urls, start, mid);
            DownloadAction rightAction = new DownloadAction(urls, mid, end);
            invokeAll(leftAction, rightAction);
        }
    }
}

  在這個示例中,我們使用了 ForkJoin 框架來實現檔案下載。首先,我們建立了一個 DownloadAction 類,繼承自 RecursiveAction 類,表示一個遞迴操作。在 compute 方法中,我們首先判斷當前操作的 URL 是否為一個,如果是,則直接呼叫 HttpUtil.download 方法進行檔案下載。如果不是,則將 URL 列表分為兩半,分別建立兩個子任務進行處理,然後使用 invokeAll 方法將這兩個子任務提交到執行緒池中並等待它們完成。

  在 main 方法中,我們首先建立了一個 ForkJoinPool 物件,然後呼叫 invoke 方法來執行 DownloadAction 操作。在這裡,我們使用了預設的執行緒池,也可以根據需要建立自定義的執行緒池。

使用Java並行流

import java.util.Arrays;
import java.util.List;

public class ParallelStreamExample {

    public static void main(String[] args) {
        List<String> urls = Arrays.asList(
            "https://example.com/file1.txt", 
            "https://example.com/file2.txt", 
            "https://example.com/file3.txt", 
            "https://example.com/file4.txt", 
            "https://example.com/file5.txt"
        );
        urls.parallelStream().forEach(url -> HttpUtil.download(url));
        System.out.println("檔案下載完成");
    }
}

  在這個示例中,我們使用了 Java 並行流來實現檔案下載。首先,我們建立了一個 URL 列表,然後使用 parallelStream 方法將其轉換為並行流。接著,我們使用 forEach 方法遍歷並行流中的每個 URL,並使用 HttpUtil.download 方法進行檔案下載。在這個過程中,Java 會自動將並行流中的元素分配給多個執行緒並行執行,以提高程式的效能。

Java 並行流是什麼?

  好了,相信看了上面的案例,應該對並行流有了一個簡單的認識了吧。讓原本又醜又長的程式碼,一下就變得眉清目秀了。所以那讓我們進一步的來了解它吧。

  Java 並行流是 Java 8 中新增的一個特性,它提供了一種便捷的方式來進行併發計算。在傳統的 Java 程式設計中,為了利用多核處理器的效能,我們需要手動編寫多執行緒程式碼。但是多執行緒程式設計非常複雜,容易出現死鎖、競態條件等問題,給我們帶來了很大的困擾。而 Java 並行流則提供了一種更加簡單、易用、安全的併發程式設計方式,可以讓我們更加輕鬆地編寫高效的併發程式。

  Java 並行流的核心是將資料集合分成多個小塊,然後在多個處理器上並行處理,最後將結果合併成一個結果集。使用 Java 並行流可以有效地利用多核處理器的效能,提升程式執行效率。此外,Java 並行流還提供了一系列的中間操作和終止操作,可以方便地進行資料篩選、對映、過濾等操作。

Java並行流的實現原理?

  Java 並行流是基於 Fork/Join 框架實現的,它使用了多執行緒來處理流操作。具體來說,Java 並行流的實現原理如下:

  1. 拆分資料

  當並行流操作開始時,資料會被拆分成多個小塊。每個小塊都會被分配給不同的執行緒去處理。

  1. 執行任務

  每個執行緒會獨立地執行任務。執行緒會使用 fork/join 框架將自己的任務拆分成更小的子任務,並將這些子任務分配給其他執行緒。

  1. 合併結果

  當所有執行緒完成任務後,它們會將自己的結果合併到一起。這個過程類似於 reduce 操作,不同之處在於它是並行的。

  Java 並行流的是基於 Fork/Join 框架實現的,而Fork/Join 框架是 Java 7 引入的一個用於平行計算的框架,它基於工作竊取演算法,可以將一個大任務拆分成多個小任務,每個執行緒獨立地處理一個小任務。在 Java 8 中,透過對 Stream 介面的擴充套件,使得平行計算更加容易實現。

  需要注意的是,Java 並行流在執行操作時,會根據當前計算機的 CPU 核心數來確定並行執行緒的數量,如果並行執行緒數量過多,會造成過多的上下文切換,反而會降低程式的效能。因此,在使用並行流時需要注意控制並行執行緒的數量。

三種方式對比

   在檔案下載這個例子中,我們使用了多執行緒、ForkJoin 框架和 Java 並行流三種方式來實現。我們來對比一下這三種方式的優缺點。

1. 多執行緒方式

優點:

  • 可以手動控制執行緒的數量,適用於對執行緒數量有特殊要求的場景。
  • 可以使用執行緒池來重用執行緒,減少執行緒建立和銷燬的開銷。
  • 可以使用 waitnotify 等機制來實現執行緒間的通訊和協作。

缺點:

  • 需要手動編寫執行緒的建立和銷燬程式碼,程式碼複雜度較高。
  • 執行緒之間的協作和通訊需要手動實現,容易出現死鎖等問題。
  • 程式碼的可讀性和可維護性較差。

2. ForkJoin 框架方式

優點:

  • 可以自動地將任務拆分成更小的子任務,並將子任務分配給多個執行緒並行執行,簡化了程式碼實現。
  • 可以透過調整並行度來最佳化效能,提高程式碼的靈活性。
  • 可以使用預設的執行緒池或自定義的執行緒池來管理執行緒。

缺點:

  • 不適用於 IO 密集型操作,僅適用於 CPU 密集型操作。
  • 執行緒之間的協作和通訊需要手動實現,容易出現死鎖等問題。

3. Java 並行流方式

優點:

  • 可以使用函數語言程式設計的方式簡化程式碼實現,程式碼可讀性較高。
  • 可以自動地將資料分配給多個執行緒並行處理,簡化了程式碼實現。
  • 可以根據需要選擇並行度來最佳化效能。
  • 可以透過流水線方式最佳化程式碼效能,提高程式碼的靈活性。

缺點:

  • 不適用於 IO 密集型操作,僅適用於 CPU 密集型操作。
  • 對於一些特殊的操作,例如排序和去重,可能需要手動調整程式碼才能使用並行流。

總結

  Java並行流可以讓多執行緒程式設計變得更加簡單易懂,減少程式設計中的併發問題,提高程式碼質量和可維護性。幫助開發人員更加輕鬆地實現任務並行,充分利用多核處理器的效能,加快程式的執行速度。但是雖然並行流有諸多優點,但是還需要根據具體場景來選擇合適的方式。如果是 IO 密集型操作,我們應該使用多執行緒或者 Java NIO 等技術來實現;如果是 CPU 密集型操作,我們可以使用 ForkJoin 框架或者 Java 並行流來實現。

結尾

  如果覺得對你有幫助,可以多多評論,多多點贊哦,也可以到我的主頁看看,說不定有你喜歡的文章,也可以隨手點個關注哦,謝謝。

  我是不一樣的科技宅,每天進步一點點,體驗不一樣的生活。我們下期見!

相關文章