首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

多線程任務分派策略:從下載工具啟發到 Java 實作

     編輯:Java 相關知識

多線程下載技術由來已久,早期的 FlashGet、NetAnts 等工具便善用 HTTP 協定的 Range 標頭,將檔案切分為多個區塊並行下載,最後合併成完整檔案。這種「分而治之」的思維,同樣適用於一般性的任務分派問題:給定一個待執行任務列表與指定線程數,如何將任務公平、高效地分配給各線程?

核心分配策略

採用 連續分段 + 餘數前置 原則:

  1. 先計算每個線程的基礎任務數 minTaskCount = 總任務數 / 線程數。
  2. 剩餘任務 remainTaskCount = 總任務數 % 線程數 依序分配給前幾個線程,每個多分配一筆。
  3. 若線程數多於任務數(僧多粥少),實際啟動線程數等於任務數,一對一執行。

此策略保證任務在數量上均衡,且每線程處理的任務在邏輯上保持連續,便於追蹤與除錯。缺點是屬於靜態分配,快線程完成後無法協助慢線程(無工作盜演機制)。

完整 Java 實作

以下程式碼整合了任務分發器 TaskDistributor、任務模型 Task 以及工作線程 WorkThread,採用命令模式封裝任務執行邏輯,若搭配監聽器(觀察者模式)即可即時反映進度,彷彿下載工具的區塊著色跳動。

package com.unmi.common;

import java.util.ArrayList;
import java.util.List;

/**
 * 任務分發器:將任務列表按策略切分給多線程
 * @author Unmi
 */
public class TaskDistributor {

    /** 測試入口 */
    public static void main(String[] args) {
        // 準備 108 筆任務
        List<Task> taskList = new ArrayList<>();
        for (int i = 0; i < 108; i++) {
            taskList.add(new Task(i));
        }

        int threadCount = 5; // 指定 5 條線程
        List<Task>[] taskListPerThread = distributeTasks(taskList, threadCount);

        System.out.println("實際啟動線程數:" + taskListPerThread.length);
        for (int i = 0; i < taskListPerThread.length; i++) {
            new WorkThread(taskListPerThread[i], i).start();
        }
    }

    /**
     * 核心分配演算法
     * @param taskList 待分派任務列表
     * @param threadCount 期望線程數
     * @return 每個元素對應一線程的任務子列表
     */
    public static List<Task>[] distributeTasks(List<Task> taskList, int threadCount) {
        int minTaskCount = taskList.size() / threadCount;
        int remainTaskCount = taskList.size() % threadCount;

        // 僧多粥少時,實際線程數 = 任務數
        int actualThreadCount = minTaskCount > 0 ? threadCount : remainTaskCount;

        @SuppressWarnings("unchecked")
        List<Task>[] taskListPerThread = new List[actualThreadCount];
        int taskIndex = 0;
        int remainIndices = remainTaskCount;

        for (int i = 0; i < actualThreadCount; i++) {
            taskListPerThread[i] = new ArrayList<>();

            // 分配基礎任務
            if (minTaskCount > 0) {
                for (int j = taskIndex; j < minTaskCount + taskIndex; j++) {
                    taskListPerThread[i].add(taskList.get(j));
                }
                taskIndex += minTaskCount;
            }

            // 餘數前置分配
            if (remainIndices > 0) {
                taskListPerThread[i].add(taskList.get(taskIndex++));
                remainIndices--;
            }
        }

        // 列印分配結果
        for (int i = 0; i < taskListPerThread.length; i++) {
            List<Task> list = taskListPerThread[i];
            System.out.printf("線程 %d 任務數:%d 區間[%d,%d]%n",
                    i, list.size(), list.get(0).getTaskId(), list.get(list.size() - 1).getTaskId());
        }
        return taskListPerThread;
    }
}

/** 任務模型:支援狀態流轉 (READY → RUNNING → FINISHED) */
class Task {
    public static final int READY = 0, RUNNING = 1, FINISHED = 2;
    private int status = READY;
    private final int taskId;

    public Task(int taskId) { this.taskId = taskId; }

    public void execute() {
        setStatus(RUNNING);
        System.out.println("當前線程:" + Thread.currentThread().getName()
                + " | 任務 ID:" + taskId);
        try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
        setStatus(FINISHED);
    }

    public void setStatus(int status) { this.status = status; }
    public int getTaskId() { return taskId; }
}

/** 自訂工作線程:持有專屬任務列表並依序執行 */
class WorkThread extends Thread {
    private final List<Task> taskList;
    private final int threadId;

    public WorkThread(List<Task> taskList, int threadId) {
        this.taskList = taskList;
        this.threadId = threadId;
    }

    @Override
    public void run() {
        for (Task task : taskList) task.execute();
    }
}

執行結果範例

線程 0 任務數:22 區間[0,21]
線程 1 任務數:22 區間[22,43]
線程 2 任務數:22 區間[44,65]
線程 3 任務數:21 區間[66,86]
線程 4 任務數:21 區間[87,107]
實際啟動線程數:5
當前線程:Thread-0 | 任務 ID:0
當前線程:Thread-1 | 任務 ID:22
...

進階延伸方向

  • 動態工作盜演:引入共享任務佇列,閒置線程可領取剩餘任務,參考 ForkJoinPool 設計。
  • 進度監控:為 Task 加入狀態變更監聽器,配合 Swing/JavaFX 即時繪製進度條。
  • 線程池化:改用 ExecutorService 管理線程生命週期,避免頻繁建立/銷毀開銷。

這個基礎實作雖簡單,卻是理解平行任務分派的良好起點。實務上可根據任務粒度、執行時間變異度選擇靜態或動態策略,甚至混合使用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved