原文: https://howtodoinjava.com/java/multi-threading/executor-framework-tutorial/

与 JDK 5 一起发布的 Java 执行器框架java.util.concurrent.Executor)用于运行Runnable对象,而无需每次都创建新线程,并且主要是重新使用已经创建的线程。

我们都知道有两种方法可以在 Java 中创建线程。 如果您想了解有关它们比较的更多信息,请阅读如何在 Java中创建线程。

在 Java 中创建线程是一个非常昂贵的过程,其中还包括内存开销。 因此,如果我们可以在创建后重新使用这些线程来运行将来的可运行对象,则是一个好主意。 在此执行器框架教程中,我将编写一些演示程序来演示Executor的用法,然后我们将讨论在设计下一个多线程应用程序时需要牢记的一些最佳实践。

1. Java 执行器框架示例

在演示应用程序中,我们有两个任务正在运行。 两者都不会终止,并且都应在应用程序的生命周期内运行。 我将尝试编写一个主包装器类,例如:

  • 如果有任何任务引发异常,则应用程序将捕获该异常并重新启动该任务。
  • 如果有任何任务运行完毕,应用程序将注意到并重新启动任务。

下面是上述所需应用程序的代码示例。

  1. package com.howtodoinjava.multiThreading.executors;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.Future;
  5. public class DemoExecutorUsage {
  6. private static ExecutorService executor = null;
  7. private static volatile Future taskOneResults = null;
  8. private static volatile Future taskTwoResults = null;
  9. public static void main(String[] args) {
  10. executor = Executors.newFixedThreadPool(2);
  11. while (true)
  12. {
  13. try
  14. {
  15. checkTasks();
  16. Thread.sleep(1000);
  17. } catch (Exception e) {
  18. System.err.println("Caught exception: " + e.getMessage());
  19. }
  20. }
  21. }
  22. private static void checkTasks() throws Exception {
  23. if (taskOneResults == null
  24. || taskOneResults.isDone()
  25. || taskOneResults.isCancelled())
  26. {
  27. taskOneResults = executor.submit(new TestOne());
  28. }
  29. if (taskTwoResults == null
  30. || taskTwoResults.isDone()
  31. || taskTwoResults.isCancelled())
  32. {
  33. taskTwoResults = executor.submit(new TestTwo());
  34. }
  35. }
  36. }
  37. class TestOne implements Runnable {
  38. public void run() {
  39. while (true)
  40. {
  41. System.out.println("Executing task one");
  42. try
  43. {
  44. Thread.sleep(1000);
  45. } catch (Throwable e) {
  46. e.printStackTrace();
  47. }
  48. }
  49. }
  50. }
  51. class TestTwo implements Runnable {
  52. public void run() {
  53. while (true)
  54. {
  55. System.out.println("Executing task two");
  56. try
  57. {
  58. Thread.sleep(1000);
  59. } catch (Throwable e) {
  60. e.printStackTrace();
  61. }
  62. }
  63. }
  64. }

请不要忘记在帖子结尾阅读最佳实践。

2. Java 执行器框架 – MultiRunnable

不必每个Runnable都在单独的线程中执行。 有时,我们需要在单个线程中执行多个作业,并且每个作业都是Runnable的实例。 要设计此类解决方案,应使用MultiRunnable。 这个MultiRunnable对象不过是需要执行的可运行对象的集合。 唯一的补充是,该MultiRunnable库本身也是Runnable

以下是需要在单个线程中执行的任务列表。

package com.howtodoinjava.multiThreading.executors;

public class TaskOne implements Runnable {
    @Override
    public void run() {
        System.out.println("Executing Task One");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class TaskTwo implements Runnable {
    @Override
    public void run() {
        System.out.println("Executing Task Two");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class TaskThree implements Runnable {
    @Override
    public void run() {
        System.out.println("Executing Task Three");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

让我们创建上述任务的MultiRunnable包装器。

package com.howtodoinjava.demo.multiThread;

import java.util.List;

public class MultiRunnable implements Runnable {

    private final List<Runnable> runnables;

    public MultiRunnable(List<Runnable> runnables) {
        this.runnables = runnables;
    }

    @Override
    public void run() {
        for (Runnable runnable : runnables) {
             new Thread(runnable).start();
        }
    }
}

现在可以在下面的程序中以这种方式执行上面的multi runnable

package com.howtodoinjava.demo.multiThread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MultiTaskExecutor {

    public static void main(String[] args) {

        BlockingQueue<Runnable> worksQueue = new ArrayBlockingQueue<Runnable>(10);
        RejectedExecutionHandler rejectionHandler = new RejectedExecutionHandelerImpl();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 10, TimeUnit.SECONDS, worksQueue, rejectionHandler);

        executor.prestartAllCoreThreads();

        List<Runnable> taskGroup = new ArrayList<Runnable>();
        taskGroup.add(new TestOne());
        taskGroup.add(new TestTwo());
        taskGroup.add(new TestThree());

        worksQueue.add(new MultiRunnable(taskGroup));
    }
}

class RejectedExecutionHandelerImpl implements RejectedExecutionHandler
{
    @Override
    public void rejectedExecution(Runnable runnable,
            ThreadPoolExecutor executor)
    {
        System.out.println(runnable.toString() + " : I've been rejected ! ");
    }
}

3. Java 执行器框架最佳实践

  1. 始终针对静态分析工具(例如 PMDFindBugs)运行 Java 代码,以查找更深层次的问题。 它们对于确定将来可能出现的丑陋情况非常有帮助。
  2. 始终与高级人员进行交叉检查并更好地计划代码审查,以在执行过程中检测并可能在代码中出现死锁或活锁。 在大多数情况下,在应用程序中添加运行状况监视器以检查正在运行的任务的状态是一个很好的选择。
  3. 在多线程程序中,也要养成捕获错误的习惯,而不仅仅是异常。 有时会发生意想不到的事情,除了异常之外,Java 还会向您抛出错误。
  4. 使用退避开关,因此,如果出现问题并且无法恢复,您就不会急于启动另一个循环来升级情况。 相反,您需要等到情况恢复正常后再重新开始。
  5. 请注意,执行器的全部目的是抽象出执行的细节,因此除非明确说明,否则不能保证顺序。

学习愉快!