• Spring 中使用 Java 5.0 Executor


    Java 5.0 新增了一个并发工具包 java.util.concurrent,该工具包由 DougLea 设计并作为 JSR-166 添加到 Java 5.0 中。这是一个非常流行的并发工具包。它提供了功能强大的、高层次的线程构造器,包含执行器、线程任务框架、线程安全队列、计时器、锁(包含原子级别的锁)和其他一些同步的基本类型。

    执行器 Executor 是并发工具包中一个重要的类,它对 Runnable 实例的执行进行了抽象,实现者可以提供具体的实现,如简单地以一个线程来运行 Runnable,或者通过一个线程池为 Runnable 提供共享线程。

    因为 Executor 是 Java 5.0 新增的类,所以 Java 5.0 提供的实现类大多拥有线程池的内在支持。Spring 为 Executor 处理引入了一个新的抽象层,以便将线程池引入 Java 1.3 和 Java 1.4 环境中,同时屏蔽掉 Java 1.3、1.4、5.0 及 JavaEE 环境中线程池实现的差异。

    1.了解 Java 5.0 的 Executor

    java.util.concurrent.Executor 接口的主要目的是将“任务提交”和“任务执行”分离解耦。该接口定义了任务提交的方法,实现者可以提供不同的任务执行机制,指定不同的线程使用规则和调度方案。

    Executor 只有一个方法:void execute(Runnable command),它接收任何实现了 Runnable 的实例,这个实例代表一个待执行的任务。可以使用如下代码提交任务:

    Executor executor  = new Executo();
    executor.executor(new RunnableTask1()); //提交一个任务
    executor.executor(new RunnableTask2()); //提交另一个任务

    Executor 本身并没有要求实现者以何种方式执行这些任务,一个简单的实现类甚至可以在提交任务时,立即在主线程中执行它们。下面是一个 Executor 最简单的实现:

    public class SimpleExecutor implements Executor{
        public void execute(Runnable r){
            r.run(); //①在提交时直接执行任务
        }
    }

    但在更多的情况下,需要在一个异步线程中执行任务,而非在主线程中执行任务。下面是一个稍微有意义的实现,它为每个任务开启一个新的执行线程。

    public class ThreadPerTaskExecutor implements Executor{
        public void execute(Runnable r){
            new Thread(r).start();  //①在新的执行线程中执行任务
        }
    }

    但以上这些粗陋的实现并没有多大的实际应用价值,这也不符合 Executor 的设计初衷。真正有意义的实现需要引入线程、列队、调度等机制,这样的执行器才更贴近现实的需求。

    Executor 接口还有两个子接口:ExecutorService 和 ScheduledExecutorService。ExecutorService 添加了结束任务的管理方法,此外,在提交任务时还可以获取一个 Future 实例,以便通过这个实例跟踪异步任务的执行情况。而 ScheduledExecutorService 可以对任务进行调度,如指定执行的延迟时间及执行的周期。如 ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) 方法将安排一个任务在延迟一段时间后执行。

    Java 5.0 本身提供的 ThreadPoolExecutor 类实现了 Executor 和 ExecutorService 这两个接口,它使用一个线程池对提交的任务进行调度。如果需要处理数量巨大的短小并发任务(如 Web 服务器、数据库服务器、邮件服务器之类的应用程序需要处理来自远程的大量短小的任务),则采用线程池可以带来明显的好处。为每个请求创建一个新线程的开销很大,对于短小的任务,线程创建和销毁的时间可能比处理实际任务的时间还要长。除此以外,活动的线程需要消耗资源,过多的线程将导致大量的内存占用。线程池能让多个任务重用同一个线程,线程创建的开销被分摊到多个任务上。此外,由于在任务到达时线程已经存在,所以也消除了任务执行时因创建线程所带来的延迟。可以通过适当地调整线程池中的参数,当任务的数目超过某个阈值时,强制新任务排队等待,直到获得一个线程来处理,从而防止资源无限制消耗而引发的系统问题。

    ThreadPoolExecutor 的子类 ScheduledThreadPoolExecutor 实现了 ScheduledExecutorService 接口,添加了对任务的调度功能,如指定延迟一小段时间后执行任务,让任务周期性执行。该类明显优于 Java 1.3 中的 Timer,因为它通过内建的线程池让每个任务在独立的执行线程中执行,而非让所有任务在单一的背景线程中执行。Timer 经常出现的时间漂移、任务挤压等问题基本上得到了规避。

    在 java.util.concurrent 中为创建这些接口实例提供了一个综合性的工厂类 Executors,它拥有以下众多方便的静态工厂方法。

    (1)public static ExecutorService newFixedThreadPool(int nThreads):创建一个线程池,重复使用一组固定的线程执行任务。

    (2)public static ExecutorService newCachedThreadPool():线程池是动态的,不够用时将创建新的线程,长时间不用的线程将被收回。

    (3)public static ScheduledExecutorService newScheduledThreadPool(int corePooISize,ThreadFactory threadFactory):创建一个线程池,可在指定延迟后执行或者定期执行。

    来看一个具体的例子,如下面代码所示。

    public class ExecutorExample {
        private Executor executor;//①声明一个执行器
        public void setExecutor(TaskExecutor executor) {
            this.executor = executor;
        }
        public void executeTasks() {//②用执行器执行多个任务
            for (int i = 0; i < 6; i++) {
                executor.execute(new SimpleTask("task" + i));
            }
        }
        public static void main(String[] args) {
            ExecutorExample ee = new ExecutorExample();
            ee.setExecutor(Executors.newFixedThreadPool(3));//③通过工厂类创建一个带3个线程的固定线程池的执行器
            ee.executeTasks();
        }
    }
    class SimpleTask implements Runnable {//④任务类
        private String taskName;
        public SimpleTask(String taskName) {
            this.taskName = taskName;
        }
        public void run() {
            System.out.println("do " + taskName + "... in Thread:"
                    + Thread.currentThread().getId());
        }
    }

    运行以上代码,输出以下信息

    do task0... in Thread:7
    do task1... in Thread:8
    do task2... in Thread:9
    do task3... in Thread:7
    do task5... in Thread:9
    do task4... in Thread:8

    可见,这6个任务共享了线程池中的3个线程。由于 ExecutorService 用线程池中的3个线程服务于提交的任务,从而避免了为每个任务创建独立线程的代价,具有更高的运行性能。

    2.Spring 对 Executor 所提供的抽象

    Spring 的 org.springframework.core.task.TaskExecutor 接口等同于 java.util.concurrent.Executor 接口。该接口和 Java 5.0 的 Executor 接口拥有相同的 execute(Runnable task) 方法。TaskExecutor 拥有一个 SchedulingTaskExecutor 子接口,新增了任务调度规则定制的功能。

    在 Spring 发行包中预定义了一些实现,它们可以满足大部分应用要求,一般情况下,用户不必自行编写实现类。下面是 TaskExecutor 的实现类。

    SyncTaskExecutor:位于 org.springframework.core.task 包中,实现了 TaskExecutor 接口。这个实现不会异步执行任务:相反,每次调用都在发起调用的主线程中执行。

    下面是 SchedulingTaskExecutor 的实现类。

    (1)SimpleAsyncTaskExecutor:位于 org.springframework.core.task 包中。该类没有使用线程池,在每次执行任务时都创建一个新线程。但是,它依然支持对并发总数设限,当超过并发总数限制时,阻塞新的任务直到有可用的资源。

    (2)ConcurrentTaskExecutor:位于 org.springframework.scheduling.concurrent 包中。该类是 Java 5.0 的 Executor 的适配器,以便将 Java 5.0 的 Executor当作 Spring 的 TaskExecutor 使用。

    (3)SimpleThreadPoolTaskExecutor:位于org.springframework.scheduling.quartz 包中。该类实际上是继承于 Quartz 的 SimpleThreadPool 类的子类,它将监听 Spring 的生命周期回调。当用户有线程池,需要在和非 Quartz 组件中共用时,该类可以发挥它的用处。

    (4)ThreadPoolTaskExecutor:位于  org.springframework.scheduling.concurrent 包中。该类只能在 Java 5.0 中使用,它暴露了一些属性,方便在 Spring 中配置一个 java.util.concurrent.ThreadPoolExecutor,并把它包装成 TaskExecutor。

    (5)TimerTaskExecutor:位于 org.springframework.scheduling.timer 包中。该类使用一个 Timer 作为其后台实现。

    上面代码中的 ExecutorExample 只能在Java 5.0 下运行,如果用 Spring 的 TaskExecutor 替换①处的 Executor,当程序需部署到低版本的 Java 环境中时,仅需要选择一个合适的实现就可以了。

    public class ExecutorExample {
        private TaskExecutor executor;//①使用Spring的TaskExecutor替换Java 5.0的Executor
        public void setExecutor(TaskExecutor executor) {
            this.executor = executor;
        }
        public void executeTasks() {//②用执行器执行多个任务
            for (int i = 0; i < 6; i++) {
                executor.execute(new SimpleTask("task" + i));
            }
        }
        public static void main(String[] args) {
            ExecutorExample ee = new ExecutorExample();
            ee.setExecutor(new SimpleAsyncTaskExecutor());//②使用Spring的TaskExecutor实现类
            ee.executeTasks();
        }
    }
    class SimpleTask implements Runnable {
        ...
    }

    如果将 ExecutorExample 配置成一个 Bean,通过注入的方式提供 executor 属性,就可以方便地选用不同的实现版本。如果是在 Java 5.0 版本中,则用户可以选用 ThreadPoolTaskExecutor;而在 JDK 低版本中则可以使用 SimpleAsyncTaskExecutor。这样,程序就可以在不同的 JDK 版本中进行移植。

  • 相关阅读:
    lr 增强窗格中,如何生成调试信息?
    lr 自带的例子,如何进行关联,通过代码的函数进行实现
    lr11 录制脚本时候,无法自动启动ie,查了网上很多方法都未解决?
    loadrunner11 录制脚步不成功,在录制概要出现“No Events were detected”,浮动窗口总是显示“0 Events”,解决办法
    loadrunner11 安装及破解教程来自百度文库
    安装loadrunner11 ,出现如下错误如何解决?
    回收站数据删除了,如何进行恢复?
    网管工作方面——————打印机删除了然后开机重启他依然存在,如何解决
    Windows 不能在 本地计算机 启动 SQL Server 服务 错误代码126
    Sorry, the page you are looking for is currently unavailable. Please try again later. Nginx
  • 原文地址:https://www.cnblogs.com/jwen1994/p/11361457.html
Copyright © 2020-2023  润新知