线程执行器(二)

6. 在执行器中延时执行任务

  如果不想让任务马上被执行,而想让任务在过一段时间之后才被执行,或者任务能够被周期性地执行。为了达到这个目的,执行器框架提供了ScheduledThreadPoolExecutor类。

  下面我们将学习如何创建ScheduledThreadPoolExecutor执行器,以及如何使用它在经过一个给定的时间后开始执行任务。

1. 创建一个名为Task的类,并实现Callable接口,接口的泛型参数为String类型。

import java.util.Date;
import java.util.concurrent.Callable;

public class Task implements Callable<String> {
    private String name;

    public Task(String name){
        this.name = name;
    }

    @Override
    public String call() throws Exception {
        System.out.printf("%s: Starting at : %s\n", name, new Date());
        return "Hello, world";
    }

}

2. 实现范例的主类Main,并实现main()方法。

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) {
        ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1);
        System.out.printf("Main: Starting at: %s\n", new Date());
        for(int i=0;i<5;i++){
            Task task = new Task("Task "+i);
            executor.schedule(task, i+1, TimeUnit.SECONDS);
        }
        //结束执行器
        executor.shutdown();
        //等待所有的任务执行结束
        try {
            executor.awaitTermination(1, TimeUnit.DAYS);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.printf("Main: Ends at: %s\n", new Date());
    }
}

3. 程序运行结果如下

Main: Starting at: Sun Oct 25 15:51:23 CST 2015
Task 0: Starting at : Sun Oct 25 15:51:24 CST 2015
Task 1: Starting at : Sun Oct 25 15:51:25 CST 2015
Task 2: Starting at : Sun Oct 25 15:51:26 CST 2015
Task 3: Starting at : Sun Oct 25 15:51:27 CST 2015
Task 4: Starting at : Sun Oct 25 15:51:28 CST 2015
Main: Ends at: Sun Oct 25 15:51:28 CST 2015

  也可以使用Runnable接口来实现任务,因为ScheduledThreadPoolExecutor类的schedule()方法可以同时接受这两种类型的任务。

  虽然ScheduledThreadPoolExecutor是ThreadPoolExecutor类的子类,因为继承了ThreadPoolExecutor类所有的特性。但是,Java推荐仅在开发定时任务程序时采用ScheduledThreadPoolExecutor类。

  在调用shutdown()方法而仍有待处理的任务需要执行时,可以配置ScheduledThreadPoolExecutor的行为。默认的行为是不论执行器是否结束,待处理的任务仍将被执行。但是,通过调用ScheduledThreadPoolExecutor类的setExecuteExistingDelayedTasksAfterShutdownPolicy()方法则可以改变这个行为。传递false参数给这个方法,执行shutdown()方法之后,待处理的任务将不会被执行。

7. 在执行器中周期性执行任务

  当发送一个任务给ThreadPoolExecutor类执行器后,根据执行器的配置,它将尽快地执行这个任务。当任务执行结束后,这个任务就会从执行器中删除;如果想再次执行这个任务,则需要再次发送这个任务到执行器。

  但是,执行器框架提供了ScheduledThreadPoolExecutor类来执行周期性的任务。

  下面我们将学习如何使用这个类的功能来计划执行周期性的任务。

1. 创建一个名为Task的类,并实现Runnable接口。

import java.util.Date;

public class Task implements Runnable {
    private String name;

    public Task(String name){
        this.name = name;
    }

    @Override
    public void run() {
        System.out.printf("%s: Starting at : %s\n", name, new Date());
    }

}

2. 实现范例的主类Main,并实现main()方法。

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        System.out.printf("Main: Starting at: %s\n", new Date());
        Task task = new Task("Task");
        //第一个参数为周期性执行的任务,第二个为第一次执行后延时时间,第三个为两次执行的时间周期,第四个为时间单位
        ScheduledFuture<?> result = executor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);
        try {
            for(int i=0;i<10;i++){
                System.out.printf("Main: Delay: %d\n", result.getDelay(TimeUnit.MILLISECONDS));
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //结束执行器
        executor.shutdown();
        //将线程休眠5秒,等待周期性的任务全部执行完成
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.printf("Main: Finished at: %s\n", new Date());
    }
}

3. 程序运行结果如下

Main: Starting at: Sun Oct 25 16:25:23 CST 2015
Main: Delay: 999
Main: Delay: 499
Main: Delay: 0
Task: Starting at : Sun Oct 25 16:25:24 CST 2015
Main: Delay: 1499
Main: Delay: 999
Main: Delay: 499
Main: Delay: 0
Task: Starting at : Sun Oct 25 16:25:26 CST 2015
Main: Delay: 1499
Main: Delay: 999
Main: Delay: 499
Task: Starting at : Sun Oct 25 16:25:28 CST 2015
Main: Finished at: Sun Oct 25 16:25:33 CST 2015

  ScheduledThreadPoolExecutor还提供了其他方法来安排周期性任务的运行,比如scheduleWithFixedRate()方法。这个方法与scheduleAtFixedRate()方法具有相同的参数,但是略有一些不同需要注意。在scheduleAtFixedRate()方法中,第3个参数表示任务两次开始时间的间隔,而在scheduleWithFixedRate()方法中,第3个参数表示任务上一次执行结束的时间与下一次开始执行的时间的间隔

  也可以配置ScheduledThreadPoolExecutor实现shutdown()方法的行为,默认行为是当调用shutdown()方法后,定时任务就结束了。可以通过ScheduledThreadPoolExecutor类的setContinueExistingPeriodicTasksAfterShutdownPolicy()方法来改变这个行为,传递参数为true给这个方法,这样调用shutdown()方法后,周期性任务仍将继续执行。

8. 在执行器中取消任务

  有时候,我们可能需要取消已经发送给执行器的任务。在这种情况下,可以使用Future接口的cancel()方法来执行取消操作。

  下面我们将学习如何使用这个方法取消已经发送给执行器的任务。

1. 创建一个名为Task的类。

import java.util.concurrent.Callable;

public class Task implements Callable<String> {

    @Override
    public String call() throws Exception {
        while(true){
            System.out.printf("Task: Test\n");
            Thread.sleep(100);
        }
    }

}

2. 实现范例的主类Mian,并实现main()方法。

import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) {
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
        Task task = new Task();
        System.out.println("Main: Executing the Task");
        Future<String> result = executor.submit(task);
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Main: Canceling the Task");
        //取消任务
        result.cancel(true);
        System.out.println("Main: Cancelled: "+ result.isCancelled());
        System.out.println("Main: Done: "+ result.isDone());
        executor.shutdown();
        System.out.println("Main: The executor has finished");
    }
}

3. 程序运行结果如下

Main: Executing the Task
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Main: Canceling the Task
Main: Cancelled: true
Main: Done: true
Main: The executor has finished

  如果想取消一个已经发送给执行器的任务,可以使用Future接口的cancel()方法。根据调用cancel()方法时所传递的参数以及任务的状态,这个方法的行为有些不同。

  • 如果任务已经完成,或者之前已经被取消,或者由于某种原因而不能被取消,那么方法将返回false并且任务也不能取消。
  • 如果任务在执行器中等待分配Thread对象来执行它,那么任务被取消,并且不会开始执行。如果任务已经在运行,那么它依赖于调用cancel()方法时所传递的参数。如果传递的参数为true并且任务正在运行,那么任务将被取消。如果传递的参数为false并且任务正在运行,那么任务不会被取消。

  如果Future对象所控制任务已经被取消,那么使用Future对象的get()方法时将抛出CancellationException异常。

9. 在执行器中控制任务的完成

  FutureTask类提供了一个名为done()的方法,允许在执行器任务执行结束之后,还可以执行一些代码。这个方法可以被用来执行一些后期处理操作,比如:产生报表,通过邮件发送结果或者释放一些系统资源。当任务执行完成是受FutureTask类控制时,这个方法在内部被FutureTask类调用。在任务结果设置后以及任务的状态已改变为isDone之后,无论任务是否被取消或者正常结束,done()方法都被调用。

  默认情况下,done()方法的实现为空,即没有任何具体的代码实现。我们可以覆盖FutureTask类并实现done()方法来改变这种行为。

  下面,我们将学习如何覆盖这些方法,并在任务结束后执行这些代码。

1. 创建名为ExecutableTask的类,并实现Callable接口,接口的泛型为String类型。

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

public class ExecutableTask implements Callable<String> {
    private String name;

    public ExecutableTask(String name){
        this.name = name;
    }

    @Override
    public String call() throws Exception {
        long duration = (long) (Math.random()*10);
        System.out.printf("%s: Waiting %d seconds for results.\n", name, duration);
        try {
            TimeUnit.SECONDS.sleep(duration);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return "Hello, world. I‘m "+name;
    }

    public String getName() {
        return name;
    }

}

2. 实现一个名为ResultTask的类,并继承FutureTask类。

时间: 2024-10-15 17:40:08

线程执行器(二)的相关文章

c# 多线程系列二 自定义线程执行器

看了第一篇文章,多线程系列,看到了在线程执行任务队列有了一定的了解~! 那么今天我来讲讲,怎么样构建通用的自定义线程概念! 线程执行任务,肯定要有目标,但是如果写死了,那么一个线程处理执行职能按照思路处理一类任务,显然不满足我们的实际场景的需求,那么怎么才能创建灵活的线程执行器呢! 首先我们来创建一个任务构造器! 1 /// <summary> 2 /// 线程模型执行任务 基类 3 /// </summary> 4 public abstract class BaseTask 5

[笔记][Java7并发编程实战手册]4.3 创建固定的线程执行器newFixedThreadPool线程池

[笔记][Java7并发编程实战手册]系列目录 简介 newFixedThreadPool(int nThreads, ThreadFactory threadFactory) 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在需要时使用提供的 ThreadFactory 创建新线程. newCachedThreadPool()创建的线程池的特性是:自动回收不使用的线程(终止并从缓存中移除那些已有 60 秒钟未被使用的线程),(在无可用线程的情况下)自动的为新来的task创

C++11线程指南(二)--Lambda线程实现

1. Thread with lambda function 基于前一章中的Lambda程序,我们进行了扩展,当前创建5个线程. #include<iostream> #include<thread> #include<vector> #include<algorithm> int main() { std::vector<std::thread> threadVec; for(int i=0; i<5; ++i){ threadVec.p

python基础-------进程线程(二)

Python中的进程线程(二) 一.python中的"锁" 1.GIL锁(全局解释锁) 含义: Python中的线程是操作系统的原生线程,Python虚拟机使用一个全局解释器锁(Global Interpreter Lock)来互斥线程对Python虚拟机的使用.为了支持多线程机制,一个基本的要求就是需要实现不同线程对共享资源访问的互斥,所以引入了GIL.GIL:在一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响.在

java并发之线程执行器(Executor)

线程执行器和不使用线程执行器的对比(优缺点) 1.线程执行器分离了任务的创建和执行,通过使用执行器,只需要实现Runnable接口的对象,然后把这些对象发送给执行器即可. 2.使用线程池来提高程序的性能.当发送一个任务给执行器时,执行器会尝试使用线程池中的线程来执行这个任务.避免了不断创建和销毁线程导致的性能开销. 3.执行器可以处理实现了Callable接口的任务.Callable接口类似于Runnable接口,却提供了两方面的增强: a.Callable主方法名称为call(),可以返回结果

线程执行器

通常我们使用JAVA来开发一个简单的并发应用时,会创建一些Runnable对象,然后创建对应的Thread对象来执行他们,但是,如果需要开发一个程序需要运行大量并发任务的时候,这个方法显然不合适.Java提供了执行器框架(Executor Framework)来解决这些问题. Executor Framework机制分离了任务的创建和执行.通过执行器,仅需要实现Runnable接口的对象,然后把这个对象发送给执行器即可.执行器通过创建所需要的线程来负责这些Runnable对象的创建.实例化以及运

C# 线程(二):关于线程的相关概念

From : http://kb.cnblogs.com/page/42528/ 什么是进程? 当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源. 而一个进程又是由多个线程所组成的. 什么是线程? 线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针.程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数. 什么是多线程? 多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建

Handler与线程通信(二)

1. 准备Looper对象 2. 在WorkerThread当中生成Handler对象 3. 在MainThread当中发送消息 这个过程与上一篇相反 由MainThread里面的Handler发送消息, WorkerThread里面的HandlerMessage来处理 Handler与线程通信(二)

线程总结(二)

线程同步:用来协调多个线程访问同一资源 /* * 线程同步的例子 * */ public class Test { public static void main(String[] args) { //创建两个线程并执行同一条语句 Run r=new Run(); Thread t1=new Thread(r,"t1"); Thread t2=new Thread(r,"t2"); t1.start(); t2.start(); } } class Run impl

进程与线程(二) java进程的内存模型

从我出生那天起,我就知道我有个兄弟,他桀骜不驯,但实力强悍 ,人家都叫它C+++            ----java 上回说到了,C进程的内存分配,那么一个java运行过程也是一个进程,java内存是如何分配的呢? http://blog.csdn.net/shimiso/article/details/8595564 详情请看:http://blog.csdn.net/a859522265/article/details/7282817 1.学习java,学过java多线程,没有学过多进程