Java Executor 框架最佳实践 | 开发者秘籍_开发者提升

2,715 阅读4分钟
原文链接: dev-cheats.com

在 JDK 5 中发布了 Executors framework(java.util.concurrent.Executor)来运行 Runnable 对象,不用像之前那样每次都创建新的线程,大多数情况下会复用已经创建的线程。

我们都知道在java中创建线程有两种方法。如果你想更多地阅读他们的比较,阅读这篇文章。在java中创建线程是一个非常昂贵的过程,它还包括内存开销。因此,如果我们可以重复使用这些线程,来运行 Runnable 可以更节省资源。

在本文中,我将编写一些示例程序,演示如何使用 Executor 框架,然后我们将讨论在设计多线程程序时需要记住的一些最佳实践。

如果你想了解更多关于其他多线程方面的信息,请点击此链接

基本使用示例

在我们的示例程序中,有两个任务运行,预计都不会停止运行,并且两个任务都在固定的环境下运行。 我将尝试编写一个包装类,以便于:

  1. 如果任何任务引发异常,程序将捕获它并重新启动任务。
  2. 如果任何任务运行完成,程序将通知并重新启动任务。

以下是程序的代码示例:

package com.devcheats.multithreading.executors;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class DemoExecutorUsage {

    private static ExecutorService executor = null;
    private static volatile Future taskOneResults = null;
    private static volatile Future taskTwoResults = null;

    public static void main(String[] args) {
        executor = Executors.newFixedThreadPool(2);
        while (true) {
            try {
                checkTasks();
                Thread.sleep(1000);
            } catch (Exception e) {
                System.err.println("Caught exception: " + e.getMessage());
            }
        }
    }

    private static void checkTasks() throws Exception {
        if (taskOneResults == null
                || taskOneResults.isDone()
                || taskOneResults.isCancelled()) {
            taskOneResults = executor.submit(new TestOne());
        }

        if (taskTwoResults == null
                || taskTwoResults.isDone()
                || taskTwoResults.isCancelled()) {
            taskTwoResults = executor.submit(new TestTwo());
        }
    }
}

class TestOne implements Runnable {
    public void run() {
        while (true) {
            System.out.println("Executing task one");
            try {
                Thread.sleep(1000);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }

    }
}

class TestTwo implements Runnable {
    public void run() {
        while (true) {
            System.out.println("Executing task two");
            try {
                Thread.sleep(1000);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }
}

请不要忘记在文章末尾阅读最佳实践。

在单个线程中执行多个任务

每个Runnable都不必在单独的线程中执行。有时,我们需要在单个线程中执行多个任务,每个任务都是Runnable的实例。要设计这种类型的解决方案,应该使用多运行。这个多可运行只是一个需要执行的可运行集合。除此之外,这个多runnable也是一个Runnable本身。

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

不需要在单独的线程中执行每一个 Runnable。有时候我们需要在一个线程中执行多个任务,每个任务都是一个 Runnable 实例。 要设计这种类型的程序,应该创建多个 Runnable,这些 Runnable 是需要被执行的任务集合。 另外,这个 Runnable 集合本身也是 Runnable 实例。

下面是需要在单个线程中执行多个任务的代码:

package com.devcheats.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();
        }
    }
}

我们创建一个多任务运行的包装器

package com.devcheats.demo.multithreading;

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();
        }
    }
}

现在上面的多线程可以像下面的程序这样执行:

package com.devcheats.demo.multithreading;

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 ! ");
    }
}

必须遵守的最佳实践

  1. 总是使用一些静态分析工具来运行你的Java代码,比如 PMDFindBugs 来发现深层的问题,它们对决定未来可能出现的复杂场景更有帮助。
  2. 总是和高级开发人员相互进行代码审查,以便于在代码中发现死锁或活锁,在程序中添加一个健康监视器来检查运行任务的状态,大多数场景中这样做是好的方式。
  3. 编写多线程应用时要养成捕获错误的习惯,不仅仅是异常。有时会发生意想不到的事情,可能Java会向你抛出一个Error,它不是一个异常。
  4. 使用回滚开关,如果程序出错的时候可能是无法恢复的,你不必急于启动另一个循环来升级这种情况,相反应该等待程序恢复后再重新开始。
  5. 请注意,Executors 的核心是抽象出执行的细节,因此除非明确说明,否则不保证排序。

祝你学习愉快!


原文出处:https://howtodoinjava.com/core-java/multi-threading/java-executor-framework-tutorial-and-best-practices

王爵nice 翻译