使用Java多线程实现任务分发

多线程下载由来已久,如 FlashGet、NetAnts 等工具,它们都是依懒于 HTTP 协议的支持(Range 字段指定请求内容范围),首先能读取出请求内容 (即欲下载的文件) 的大小,划分出若干区块,把区块分段分发给每个线程去下载,线程从本段起始处下载数据及至段尾,多个线程下载的内容最终会写入到同一个文件中。

    只研究有用的,工作中的需求:要把多个任务分派给Java的多个线程去执行,这其中就会有一个任务列表指派到线程的策略思考:已知:1. 一个待执行的任务列表,2. 指定要启动的线程数;问题是:每个线程实际要执行哪些任务。

    使用Java多线程实现这种任务分发的策略是:任务列表连续按线程数分段,先保证每线程平均能分配到的任务数,余下的任务从前至后依次附加到线程中——只是数量上,实际每个线程执行的任务都还是连续的。如果出现那种僧多(线程) 粥(任务) 少的情况,实际启动的线程数就等于任务数,一挑一。这里只实现了每个线程各扫自家门前雪,动作快的完成后眼见别的线程再累都是爱莫能助。

    实现及演示代码如下:由三个类实现,写在了一个 Java 文件中:TaskDistributor 为任务分发器,Task 为待执行的任务,WorkThread 为自定的工作线程。代码中运用了命令模式,如若能配以监听器,用上观察者模式来控制 UI 显示就更绝妙不过了,就能实现像下载中的区块着色跳跃的动感了,在此定义下一步的着眼点了。

    代码中有较为详细的注释,看这些注释和执行结果就很容易理解的。main() 是测试方法

 
  1. package com.alpha.thread;

  2.  
  3. import java.util.ArrayList;

  4. import java.util.List;

  5.  
  6. /**

  7. * 指派任务列表给线程的分发器

  8. */

  9. public class TaskDistributor {

  10.  
  11. /**

  12. * 测试方法

  13. * @param args

  14. */

  15. @SuppressWarnings("unchecked")

  16. public static void main(String[] args) {

  17. // 初始化要执行的任务列表

  18. List taskList = new ArrayList();

  19. for (int i = 0; i < 100; i++) {

  20. taskList.add(new Task(i));

  21. }

  22. // 设定要启动的工作线程数为 4 个

  23. int threadCount = 4;

  24. List[] taskListPerThread = distributeTasks(taskList, threadCount);

  25. System.out.println("实际要启动的工作线程数:" + taskListPerThread.length);

  26. for (int i = 0; i < taskListPerThread.length; i++) {

  27. Thread workThread = new WorkThread(taskListPerThread[i], i);

  28. workThread.start();

  29. }

  30. }

  31.  
  32. /**

  33. * 把 List 中的任务分配给每个线程,先平均分配,剩于的依次附加给前面的线程 返回的数组有多少个元素 (List) 就表明将启动多少个工作线程

  34. *

  35. * @param taskList

  36. * 待分派的任务列表

  37. * @param threadCount

  38. * 线程数

  39. * @return 列表的数组,每个元素中存有该线程要执行的任务列表

  40. */

  41. @SuppressWarnings("unchecked")

  42. public static List[] distributeTasks(List taskList, int threadCount) {

  43. // 每个线程至少要执行的任务数,假如不为零则表示每个线程都会分配到任务

  44. int minTaskCount = taskList.size() / threadCount;

  45. // 平均分配后还剩下的任务数,不为零则还有任务依个附加到前面的线程中

  46. int remainTaskCount = taskList.size() % threadCount;

  47. // 实际要启动的线程数,如果工作线程比任务还多

  48. // 自然只需要启动与任务相同个数的工作线程,一对一的执行

  49. // 毕竟不打算实现了线程池,所以用不着预先初始化好休眠的线程

  50. int actualThreadCount = minTaskCount > 0 ? threadCount

  51. : remainTaskCount;

  52. // 要启动的线程数组,以及每个线程要执行的任务列表

  53. List[] taskListPerThread = new List[actualThreadCount];

  54. int taskIndex = 0;

  55. // 平均分配后多余任务,每附加给一个线程后的剩余数,重新声明与 remainTaskCount

  56. // 相同的变量,不然会在执行中改变 remainTaskCount 原有值,产生麻烦

  57. int remainIndces = remainTaskCount;

  58. for (int i = 0; i < taskListPerThread.length; i++) {

  59. taskListPerThread[i] = new ArrayList();

  60. // 如果大于零,线程要分配到基本的任务

  61. if (minTaskCount > 0) {

  62. for (int j = taskIndex; j < minTaskCount + taskIndex; j++) {

  63. taskListPerThread[i].add(taskList.get(j));

  64. }

  65. taskIndex += minTaskCount;

  66. }

  67. // 假如还有剩下的,则补一个到这个线程中

  68. if (remainIndces > 0) {

  69. taskListPerThread[i].add(taskList.get(taskIndex++));

  70. remainIndces--;

  71. }

  72. }

  73. // 打印任务的分配情况

  74. for (int i = 0; i < taskListPerThread.length; i++) {

  75. System.out.println("线程 "

  76. + i

  77. + " 的任务数:"

  78. + taskListPerThread[i].size()

  79. + " 区间["

  80. + ((Task) taskListPerThread[i].get(0)).getTaskId()

  81. + ","

  82. + ((Task) taskListPerThread[i].get(taskListPerThread[i].size() - 1))

  83. .getTaskId() + "]");

  84. }

  85. return taskListPerThread;

  86. }

  87. }


 

 
  1. package com.alpha.thread;

  2.  
  3. /**

  4. * 要执行的任务,可在执行时改变它的某个状态或调用它的某个操作 例如任务有三个状态,就绪,运行,完成,默认为就绪态 要进一步完善,可为 Task

  5. * 加上状态变迁的监听器,因之决定UI的显示

  6. */

  7. class Task {

  8. public static final int READY = 0;

  9. public static final int RUNNING = 1;

  10. public static final int FINISHED = 2;

  11. @SuppressWarnings("unused")

  12. private int status;

  13. // 声明一个任务的自有业务含义的变量,用于标识任务

  14. private int taskId;

  15.  
  16. // 任务的初始化方法

  17. public Task(int taskId) {

  18. this.status = READY;

  19. this.taskId = taskId;

  20. }

  21.  
  22. /**

  23. * 执行任务

  24. */

  25. public void execute() {

  26. // 设置状态为运行中

  27. setStatus(Task.RUNNING);

  28. System.out.println("当前线程 ID 是:" + Thread.currentThread().getName()

  29. + " | 任务 ID 是:" + this.taskId);

  30. // 附加一个延时

  31. try {

  32. Thread.sleep(1000);

  33. } catch (InterruptedException e) {

  34. e.printStackTrace();

  35. }

  36. // 执行完成,改状态为完成

  37. setStatus(FINISHED);

  38. }

  39.  
  40. public void setStatus(int status) {

  41. this.status = status;

  42. }

  43.  
  44. public int getTaskId() {

  45. return taskId;

  46. }

  47. }


 

 
  1. package com.alpha.thread;

  2.  
  3. import java.util.List;

  4.  
  5. /**

  6. * 自定义的工作线程,持有分派给它执行的任务列表

  7. */

  8. class WorkThread extends Thread {

  9. // 本线程待执行的任务列表,你也可以指为任务索引的起始值

  10. private List<Task> taskList = null;

  11. @SuppressWarnings("unused")

  12. private int threadId;

  13.  
  14. /**

  15. * 构造工作线程,为其指派任务列表,及命名线程 ID

  16. *

  17. * @param taskList

  18. * 欲执行的任务列表

  19. * @param threadId

  20. * 线程 ID

  21. */

  22. @SuppressWarnings("unchecked")

  23. public WorkThread(List taskList, int threadId) {

  24. this.taskList = taskList;

  25. this.threadId = threadId;

  26. }

  27.  
  28. /**

  29. * 执行被指派的所有任务

  30. */

  31. public void run() {

  32. for (Task task : taskList) {

  33. task.execute();

  34. }

  35. }

  36. }


执行结果如下,注意观察每个Java多线程分配到的任务数量及区间。直到所有的线程完成了所分配到的任务后程序结束:

 
  1. 线程 0 的任务数:25 区间[0,24]

  2. 线程 1 的任务数:25 区间[25,49]

  3. 线程 2 的任务数:25 区间[50,74]

  4. 线程 3 的任务数:25 区间[75,99]

  5. 实际要启动的工作线程数:4

  6. 当前线程 ID 是:Thread-0 | 任务 ID 是:0

  7. 当前线程 ID 是:Thread-3 | 任务 ID 是:75

  8. 当前线程 ID 是:Thread-1 | 任务 ID 是:25

  9. 当前线程 ID 是:Thread-2 | 任务 ID 是:50

  10. 当前线程 ID 是:Thread-1 | 任务 ID 是:26

  11. 当前线程 ID 是:Thread-3 | 任务 ID 是:76

  12. 当前线程 ID 是:Thread-0 | 任务 ID 是:1

  13. 当前线程 ID 是:Thread-2 | 任务 ID 是:51


 

上面坦白来只算是基本功夫,贴出来还真见笑了。还有更为复杂的功能。

    像Java多线程的下载工具的确更充分利用了网络资源,而且像 FlashGet、NetAnts 都实现了:假如某个线程下载完了欲先所分配段的内容之后,会帮其他线程下载未完成数据,直到任务完成;或某一下载线程的未完成段区间已经很小了,用不着别人来帮忙时,这就涉及到任务的进一步分配。再如,以上两个工具都能动态增加、减小或中止线程,越说越复杂了,它们原本比这复杂多了,这些实现可能定义各种队列来实现,如未完成任务队列、下载中任务队列和已完成队列等。

原文来自:http://java.chinaitlab.com/line/792110.html

猜你喜欢

转载自blog.csdn.net/yangzhengjianglove/article/details/81233533
今日推荐