Java并发之任务执行器Executor

一、概述

以前线程Thread既表示执行的任务,又表示执行的机制。在JDK1.5中,java并发框架提供了一种“执行服务”的相关API,它将"任务的执行"和"任务的提交“相分离,”执行服务“封装了任务执行的细节,对于任务提交者来说,它可进一步聚焦于任务本身,如任务提交、获取任务执行后的结果、取消任务而不需要关注任务执行的细节,如线程的创建、任务的调试、线程的复用或关闭等。

  • Executor接口:这个接口也是整个线程池中最顶层的接口,提供了一个无返回值的提交任务的方法。
  • ExecutorService接口:派生自Executor接口,扩展了很过功能,例如关闭线程池,提交任务并返回结果数据、唤醒线程池中的任务等。
  • AbstractExecutorService抽象类:派生自ExecutorService接口,实现了几个非常实现的方法,供子类进行调用。
  • ScheduledExecutorService定时任务接口,派生自ExecutorService接口,拥有ExecutorService接口定义的全部方法,并扩展了定时任务相关的方法。

二、Executor接口

Executor接口也只有一个方法,这个方法接受一个Runnable类型的参数,这是个抽象方法,它无法指定任务该如何执行。它可能是新建一个线程执行任务,也可能是利用线程池中的一个线程,还可能是在调用者线程中执行。

public interface Executor {
    //提交运行任务,参数为Runnable接口对象,无返回值
    void execute(Runnable command);
}

三、ExecutorService接口

ExecutorService扩展了Executor接口,相较于Executor,主要多了两个新特性:1、手动关闭;2、支持CallableFuture接口。

package java.util.concurrent;

import java.util.List;
import java.util.Collection;

public interface ExecutorService extends Executor {

	//关闭线程池,线程池中不再接受新提交的任务,但是之前提交的任务继续运行,直到完成
    void shutdown();
	
	//关闭线程池,线程池中不再接受新提交的任务,会尝试停止线程池中正在执行的任务。
    List<Runnable> shutdownNow();
	
	//判断线程池是否已经关闭
    boolean isShutdown();
	
	//判断线程池中的所有任务是否结束,只有在调用shutdown或者shutdownNow方法之后调用此方法才会返回true。
    boolean isTerminated();

	//等待线程池中的所有任务执行结束,并设置超时时间
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
	
	//提交一个Callable接口类型的任务,返回一个Future类型的结果
    <T> Future<T> submit(Callable<T> task);
	
	//提交一个Callable接口类型的任务,并且给定一个泛型类型的接收结果数据参数,返回一个Future类型的结果
    <T> Future<T> submit(Runnable task, T result);

	//提交一个Runnable接口类型的任务,返回一个Future类型的结果
    Future<?> submit(Runnable task);

	//批量提交任务并获得他们的future,Task列表与Future列表一一对应
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
	
	//批量提交任务并获得他们的future,并限定处理所有任务的时间
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit) throws InterruptedException;
	
	//批量提交任务并获得一个已经成功执行的任务的结果
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException; 
	
	//批量提交任务并获得一个已经成功执行的任务的结果,并限定处理任务的时间
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

3.1 手动关闭

①.ExecutorService有两个关闭执行服务的方法,分别是shutdownshutdownNowshutdownshutdownNow不会阻塞等待,它们返回后不代表所有任务都结束。

  • shutdown方法调用后不会接受新的任务,但已提交的任务将继续执行(即使任务还未真正开始执行);
  • shutdownNow方法不仅不会接受新任务,而且还会终止已经提交但未执行的任务,对于正在执行的任务,一般调用Thread.interrupt()方法设置中断标志,不过线程可能不响应中断,shutdownNow会返回已提交但未执行的任务列表。

②.isShutdown返回执行服务是否被关闭的布尔值(不会等待),只要shutdownshutdownNow任意一方法被调用后,isShutdown都将返回true
③.awaitTermination方法用于等待执行服务中的所有任务完成,此方法需要设置超时时间,如果在限时间内所有任务都结束了(允许非正常结束),
④.isTerminated返回在执行服务关闭后所有任务是否已完成的布尔值,如果在此之前shutdownNowshutdown没有被调用,这里永不可能返回true

3.2 支持Callable和Future接口

①.三个submit方法都用于提交单任务

  • submit(Callable<T>)方法中入参Callable本身有返回结果;
  • submit(Runnable, T)方法在设定任务的同时可以提供一个结果,在任务结束时将返回这个结果;
  • submit(Runnable)方法入参没有提供结果,最终返回的结果是null

②.ExecutorService有两类批量提交任务的方法,invokeAllinvokeAny,它们都有两个版本,一个不限时版本、一个超时版本。

  • invokeAny:批量提交任务,只要有一个任务执行完成,就返回这个任务的结果,如果所有任务都未执行完成,则抛出异常。
  • invokeAll:批量提交任务,并返回一个List集合,其中包含每个任务的Future对象;

3.3 示例1

分别实现以下三种执行器的关闭方式:

  • 方式1:Executor,无法主动关闭,根据创建线程池的方式的不同而采取不同的关闭策略
  • 方式2:ExecutorService.shutdown(),不再接收新的任务,在当前已经提交的任务执行完毕后,关闭执行器。
  • 方式3:ExecutorService.shutdownNow(),不再接收新的任务,立刻关闭执行器,如果存在正在运行的任务,则终止这些任务。
public class Test {
    public static void main(String[] args) {
        int type = 0;
        switch (type) {
            case 0:
                //Executor无法主动关闭 根据创建线程池的方式采取不同的关闭策略
                Executor executor = Executors.newCachedThreadPool();
                //Executor只有一个方法execute():提交Runnable任务
                executor.execute(getRunnable("executor"));
                break;
            case 1:
                //ExecutorService shutdown 当正在运行的线程运行结束后,关闭线程池
                ExecutorService executorService = Executors.newCachedThreadPool();
                //ExecutorService通过submit提交任务
                executorService.submit(getRunnable("executorService"));
                executorService.shutdown();
                break;
            case 2:
                //ExecutorService shutdownNow 理解关闭线程池,正在运行的线程将被停止
                ExecutorService executorService1 = Executors.newCachedThreadPool();
                //ExecutorService通过submit提交任务
                executorService1.submit(getRunnable("executorService"));
                executorService1.shutdownNow();
                break;
            default:
                break;
        }
    }

    private static Runnable getRunnable(String executorName) {
        return () -> {
            String name = Thread.currentThread().getName();
            System.out.println(name + "--- " + executorName + " begin ...");
            try {
                Thread.sleep(1000);
                System.out.println(name + "--- " + executorName + " end .");
            } catch (InterruptedException e) {
                System.out.println(name + "--- " + executorName + " is interrupted .");
            }
        };
    }
}

方式1(Executor)运行结果:

pool-1-thread-1--- Executor begin ...
pool-1-thread-1--- Executor end .

执行器会在线程运行完之后,再等待60s之后再关闭。

方式2(shutdown)运行结果:

pool-1-thread-1--- ExecutorService begin ...
pool-1-thread-1--- ExecutorService end .

执行器会在线程运行完之后再关闭。

方式3(shutdownNow)运行结果:

pool-1-thread-1--- ExecutorService begin ...
pool-1-thread-1--- ExecutorService is interrupted .

执行器会在线程运行过程中关闭。

3.4 示例2

  • 定义一个任务(Callable)集合,其中的每个任务实现随机取数。
  • 定义一个结果(Future)集合,与任务(Callable)集合相对应。
  • 实现1:批量执行任务集合,当所有的任务执行完毕之后,输出所有的执行结果。
  • 实现2:批量执行任务集合,输出第一个执行完毕的任务的执行结果。
public class Test {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        List<Callable<Integer>> callableList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            callableList.add(() -> {
                Integer result = ThreadLocalRandom.current().nextInt(100, 2000);
                Thread.sleep(result);
                System.out.println("init " + result);
                return result;
            });
        }
        //全部任务完成
        List<Future<Integer>> futureList = executorService.invokeAll(callableList);
        System.out.println("等待所有的任务完成之后,才会得到结果集。");
        futureList.forEach(future -> {
            try {
                System.out.println("result " + future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
        Thread.sleep(1000);
        System.out.println("-----------");
        //最先完成任务
        Integer firstResult = executorService.invokeAny(callableList);
        System.out.println("得到一个最先得到的结果,立即返回;后面的任务不再运行。");
        System.out.println("result " + firstResult);
        executorService.shutdown();
    }
}

invokeAny会导致未执行完成的任务不再执行。

init 139
init 172
init 667
init 1776
init 1806
等待所有的任务完成之后,才会得到结果集。
result 1806
result 667
result 139
result 1776
result 172
-----------
init 595
得到一个最先得到的结果,立即返回;后面的任务不再运行。
result 595

四、AbstractExecutorService抽象类

AbstractExecutorService类是一个抽象类,派生自ExecutorService接口,在其基础上实现了几个比较实用的方法,提供给子类进行调用。我们还是来看下AbstractExecutorService类的源码。

4.1 newTaskFor方法

protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
	return new FutureTask<T>(runnable, value);
}

protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
	return new FutureTask<T>(callable);
}

RunnableFuture类用于获取执行结果,在实际使用时,我们经常使用的是它的子类FutureTasknewTaskFor方法的作用就是将任务封装成FutureTask对象,后续将FutureTask对象提交到线程池。

4.2 doInvokeAny方法

private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
						  boolean timed, long nanos)
	throws InterruptedException, ExecutionException, TimeoutException {
	//提交的任务为空,抛出空指针异常
	if (tasks == null)
		throw new NullPointerException();
	//记录待执行的任务的剩余数量
	int ntasks = tasks.size();
	//任务集合中的数据为空,抛出非法参数异常
	if (ntasks == 0)
		throw new IllegalArgumentException();
	ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
	//以当前实例对象作为参数构建ExecutorCompletionService对象
	// ExecutorCompletionService负责执行任务,后面调用用poll返回第一个执行结果
	ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this);
	try {
		// 记录可能抛出的执行异常
		ExecutionException ee = null;
		// 初始化超时时间
		final long deadline = timed ? System.nanoTime() + nanos : 0L;
		Iterator<? extends Callable<T>> it = tasks.iterator();
	
		//提交任务,并将返回的结果数据添加到futures集合中
		//提交一个任务主要是确保在进入循环之前开始一个任务
		futures.add(ecs.submit(it.next()));
		--ntasks;
		//记录正在执行的任务数量
		int active = 1;

		for (;;) {
			//从完成任务的BlockingQueue队列中获取并移除下一个将要完成的任务的结果。
			//如果BlockingQueue队列中中的数据为空,则返回null
			//这里的poll()方法是非阻塞方法
			Future<T> f = ecs.poll();
			//获取的结果为空
			if (f == null) {
				//集合中仍有未执行的任务数量
				if (ntasks > 0) {
					//未执行的任务数量减1
					--ntasks;
					//提交完成并将结果添加到futures集合中
					futures.add(ecs.submit(it.next()));
					//正在执行的任务数量加•1
					++active;
				}
				//所有任务执行完成,并且返回了结果数据,则退出循环
				//之所以处理active为0的情况,是因为poll()方法是非阻塞方法,可能导致未返回结果时active为0
				else if (active == 0)
					break;
				//如果timed为true,则执行获取结果数据时设置超时时间,也就是超时获取结果表示
				else if (timed) {	
					f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
					if (f == null)
						throw new TimeoutException();
					nanos = deadline - System.nanoTime();
				}
				//没有设置超时,并且所有任务都被提交了,则一直阻塞,直到返回一个执行结果
				else
					f = ecs.take();
			}
			//获取到执行结果,则将正在执行的任务减1,从Future中获取结果并返回
			if (f != null) {
				--active;
				try {
					return f.get();
				} catch (ExecutionException eex) {
					ee = eex;
				} catch (RuntimeException rex) {
					ee = new ExecutionException(rex);
				}
			}
		}
		if (ee == null)
			ee = new ExecutionException();
		throw ee;
	} finally {
		//如果从所有执行的任务中获取到一个结果数据,则取消所有执行的任务,不再向下执行
		for (int i = 0, size = futures.size(); i < size; i++)
			futures.get(i).cancel(true);
	}
}

这个方法是批量执行线程池的任务,最终返回一个结果数据的核心方法,通过源代码的分析,我们可以发现,这个方法只要获取到一个结果数据,就会取消线程池中所有运行的任务,并将结果数据返回。这就好比是很多要进入一个居民小区一样,只要有一个人有门禁卡,门卫就不再检查其他人是否有门禁卡,直接放行。

在上述代码中,我们看到提交任务使用的ExecutorCompletionService对象的submit方法,我们再来看下ExecutorCompletionService类中的submit方法,如下所示。

public Future<V> submit(Callable<V> task) {
	if (task == null) throw new NullPointerException();
	RunnableFuture<V> f = newTaskFor(task);
	executor.execute(new QueueingFuture(f));
	return f;
}

public Future<V> submit(Runnable task, V result) {
	if (task == null) throw new NullPointerException();
	RunnableFuture<V> f = newTaskFor(task, result);
	executor.execute(new QueueingFuture(f));
	return f;
}

可以看到,ExecutorCompletionServicesubmit方法有两个重载方法,一个是提交Callable任务,一个是提交Runnable任务,并且都返回Future对象。

4.3 invokeAny方法

public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
	throws InterruptedException, ExecutionException {
	try {
		return doInvokeAny(tasks, false, 0);
	} catch (TimeoutException cannotHappen) {
		assert false;
		return null;
	}
}

public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
						long timeout, TimeUnit unit)
	throws InterruptedException, ExecutionException, TimeoutException {
	return doInvokeAny(tasks, true, unit.toNanos(timeout));
}

这两个invokeAny方法本质上都是在调用doInvokeAny方法,在线程池中提交多个任务,只要返回一个结果数据即可。

直接看上面的代码,大家可能有点晕。这里,我举一个例子,我们在使用线程池的时候,可能会启动多个线程去执行各自的任务,比如线程A负责task_a,线程B负责task_b,这样可以大规模提升系统处理任务的速度。如果我们希望其中一个线程执行完成返回结果数据时立即返回,而不需要再让其他线程继续执行任务。此时,就可以使用invokeAny方法。

4.4 invokeAll方法

public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
	throws InterruptedException {
	if (tasks == null)
		throw new NullPointerException();
	ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
	//标识所有任务是否完成
	boolean done = false;
	try {
		//遍历所有任务
		for (Callable<T> t : tasks) {
			//将每个任务封装成RunnableFuture对象提交任务
			RunnableFuture<T> f = newTaskFor(t);
			//将结果数据添加到futures集合中
			futures.add(f);
			//执行任务
			execute(f);
		}
		//遍历结果数据集合
		for (int i = 0, size = futures.size(); i < size; i++) {
			Future<T> f = futures.get(i);
			//任务没有完成
			if (!f.isDone()) {
				try {
					//阻塞等待任务完成并返回结果
					f.get();
				} catch (CancellationException ignore) {
				} catch (ExecutionException ignore) {
				}
			}
		}
		//任务完成(不管是正常结束还是异常完成)
		done = true;
		//返回结果数据集合
		return futures;
	} finally {
		//如果发生中断异常InterruptedException 则取消已经提交的任务
		if (!done)
			for (int i = 0, size = futures.size(); i < size; i++)
				futures.get(i).cancel(true);
	}
}

public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
									 long timeout, TimeUnit unit)
	throws InterruptedException {
	if (tasks == null)
		throw new NullPointerException();
	long nanos = unit.toNanos(timeout);
	ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
	boolean done = false;
	try {
		for (Callable<T> t : tasks)
			futures.add(newTaskFor(t));

		final long deadline = System.nanoTime() + nanos;
		final int size = futures.size();
		for (int i = 0; i < size; i++) {
			execute((Runnable)futures.get(i));
			// 在添加执行任务时超时判断,如果超时则立刻返回futures集合
			nanos = deadline - System.nanoTime();
			if (nanos <= 0L)
				return futures;
		}
		 // 遍历所有任务
		for (int i = 0; i < size; i++) {
			Future<T> f = futures.get(i);
			if (!f.isDone()) {
				//对结果进行判断时进行超时判断
				if (nanos <= 0L)
					return futures;
				try {
					f.get(nanos, TimeUnit.NANOSECONDS);
				} catch (CancellationException ignore) {
				} catch (ExecutionException ignore) {
				} catch (TimeoutException toe) {
					return futures;
				}
				//重置任务的超时时间
				nanos = deadline - System.nanoTime();
			}
		}
		done = true;
		return futures;
	} finally {
		if (!done)
			for (int i = 0, size = futures.size(); i < size; i++)
				futures.get(i).cancel(true);
	}
}

invokeAll方法同样实现了无超时时间设置和有超时时间设置的逻辑。

无超时时间设置的invokeAll方法总体逻辑为:将所有任务封装成RunnableFuture对象,调用execute方法执行任务,将返回的结果数据添加到futures集合,之后对futures集合进行遍历判断,检测任务是否完成,如果没有完成,则调用get方法阻塞任务,直到返回结果数据,此时会忽略异常。最终在finally代码块中对所有任务是否完成的标识进行判断,如果存在未完成的任务,则取消已经提交的任务。

有超时设置的invokeAll方法总体逻辑与无超时时间设置的invokeAll方法总体逻辑基本相同,只是在两个地方添加了超时的逻辑判断。一个是在添加执行任务时进行超时判断,如果超时,则立刻返回futures集合;另一个是每次对结果数据进行判断时添加了超时处理逻辑。

invokeAll方法中本质上还是调用Executor接口的execute方法来提交任务。

4.5 submit方法

submit方法的逻辑比较简单,就是将任务封装成RunnableFuture对象并提交,执行任务后返回Future结果数据。如下所示。

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

public <T> Future<T> submit(Runnable task, T result) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task, result);
    execute(ftask);
    return ftask;
}

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

从源码中可以看出submit方法提交任务时,本质上还是调用的Executor接口的execute方法。

综上所述,在非定时任务类的线程池中提交任务时,本质上都是调用的Executor接口的execute方法。至于调用的是哪个具体实现类的execute方法,我们在后面的文章中深入分析。

五、ScheduledExecutorService接口

5.1 源码

ScheduledExecutorService接口派生自ExecutorService接口,继承了ExecutorService接口的所有功能,并提供了定时处理任务的能力,ScheduledExecutorService接口的源代码比较简单,如下所示。

package java.util.concurrent;

public interface ScheduledExecutorService extends ExecutorService {

    //延时delay时间来执行command任务,只执行一次
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);

	//延时delay时间来执行callable任务,只执行一次
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay, TimeUnit unit);

	//延时initialDelay时间首次执行command任务,之后每隔period时间执行一次
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);
												  
	//延时initialDelay时间首次执行command任务,之后每延时delay时间执行一次
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

}

至此,我们分析了线程池体系中重要的顶层接口和抽象类。

5.2 示例

  • 方式0:schedule延时执行Runnable和延时执行Callable执行一次
  • 方式1:scheduleWithFixedDelay周期性的延时执行Runnable接口 上一次任务结束和下一次任务开始之间的时间间隔是固定的=delay
  • 方式2:scheduleAtFixedRate周期性的等速率执行Runnable接口 上一次任务开始和下一次任务开始之间的时间间隔是固定的=period
  • 方式3:scheduleAtFixedRate周期性的等速率执行Runnable接口 如果任务执行时间大于period,则上一次任务结束之后,立即开始下一次任务;即period=任务执行时间
public class Test {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        int type = 2;
        switch (type) {
            case 0:
                //延时执行Runnable接口
                System.out.println("延时执行Runnable接口 : " + System.currentTimeMillis());
                scheduledExecutorService.schedule(() -> {
                    System.out.println("2秒之后 : " + System.currentTimeMillis());
                }, 2000, TimeUnit.MILLISECONDS);

                Thread.sleep(2500);
                //延时执行Callable接口
                System.out.println();
                System.out.println("延时执行Callable接口 : " + System.currentTimeMillis());
                ScheduledFuture scheduledFuture = scheduledExecutorService.schedule(() -> {
                    return System.currentTimeMillis();
                }, 2, TimeUnit.SECONDS);
                System.out.println("2秒之后 :" + scheduledFuture.get());

                //等待多长时间
                Thread.sleep(1000);
                break;
            case 1:
                //周期性的延时执行
                //初始延时,,延时
                long initDelay = 5000, delay = 3000;
                System.out.println("周期性的延时执行Runnable接口 : " + System.currentTimeMillis());
                //周期性的延时执行
                scheduledExecutorService.scheduleWithFixedDelay(getRunnable(2000L),
                        initDelay, delay, TimeUnit.MILLISECONDS);
                //等待多长时间
                Thread.sleep(20000);
                break;
            case 2:
                //初始延时,执行周期
                long initDelay1 = 5000, period = 3000;
                System.out.println("周期性的延时执行Runnable接口 : " + System.currentTimeMillis());
                //周期性的执行
                scheduledExecutorService.scheduleAtFixedRate(getRunnable(2000L),
                        initDelay1, period, TimeUnit.MILLISECONDS);
                //等待多长时间
                Thread.sleep(20000);
                break;
            case 3:
                //初始延时, 执行周期
                long initDelay2 = 5000, period1 = 2000;
                System.out.println("周期性的延时执行Runnable接口 : " + System.currentTimeMillis());
                //周期性的执行
                scheduledExecutorService.scheduleAtFixedRate(getRunnable(3000L),
                        initDelay2, period1, TimeUnit.MILLISECONDS);
                //等待多长时间
                Thread.sleep(20000);
                break;
            default:
                break;
        }
        //如果没关闭,则关闭
        if (!scheduledExecutorService.isShutdown()) {
            scheduledExecutorService.shutdown();
        }
    }

    private static Runnable getRunnable(Long millis) {
        return () -> {
            int number = ThreadLocalRandom.current().nextInt(1000, 3000);
            System.out.println("周期性的延时执行Runnable接口 [" + number + "]开始运行 : "
                    + System.currentTimeMillis());
            //模拟运行
            try {
                Thread.sleep(millis);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("周期性的延时执行Runnable接口 [" + number + "]结束运行 : "
                    + System.currentTimeMillis());
        };
    }
}

无论是Runnable还是Callable任务,都只是执行了一次。

type=1时,调用scheduleWithFixedDelay,执行结果:

2018-04-06 16:30:24 INFO - 周期性的延时执行Runnable接口 : 1523003424210
2018-04-06 16:30:29 INFO - 周期性的延时执行Runnable接口 [1513]开始运行 : 1523003429281
2018-04-06 16:30:31 INFO - 周期性的延时执行Runnable接口 [1513]结束运行 : 1523003431281
2018-04-06 16:30:34 INFO - 周期性的延时执行Runnable接口 [2579]开始运行 : 1523003434282
2018-04-06 16:30:36 INFO - 周期性的延时执行Runnable接口 [2579]结束运行 : 1523003436282
2018-04-06 16:30:39 INFO - 周期性的延时执行Runnable接口 [2347]开始运行 : 1523003439283
2018-04-06 16:30:41 INFO - 周期性的延时执行Runnable接口 [2347]结束运行 : 1523003441284

上一次任务结束 与 下一次任务开始 的间隔 = delay = 3秒

type=2时,调用scheduleAtFixedRate,执行结果:

2018-04-06 16:33:48 INFO - 周期性的延时执行Runnable接口 : 1523003628760
2018-04-06 16:33:53 INFO - 周期性的延时执行Runnable接口 [2601]开始运行 : 1523003633808
2018-04-06 16:33:56 INFO - 周期性的延时执行Runnable接口 [2189]开始运行 : 1523003636804
2018-04-06 16:33:59 INFO - 周期性的延时执行Runnable接口 [2071]开始运行 : 1523003639804
2018-04-06 16:34:02 INFO - 周期性的延时执行Runnable接口 [2399]开始运行 : 1523003642803
2018-04-06 16:34:05 INFO - 周期性的延时执行Runnable接口 [1743]开始运行 : 1523003645803
2018-04-06 16:34:08 INFO - 周期性的延时执行Runnable接口 [2351]开始运行 : 1523003648804

上一次任务开始 与 下一次任务开始 的间隔 = delay = 3秒

type=3时,调用scheduleAtFixedRate,执行结果:

2018-04-06 16:35:04 INFO - 周期性的延时执行Runnable接口 : 1523003704193
2018-04-06 16:35:09 INFO - 周期性的延时执行Runnable接口 [1614]开始运行 : 1523003709250
2018-04-06 16:35:12 INFO - 周期性的延时执行Runnable接口 [1614]结束运行 : 1523003712250
2018-04-06 16:35:12 INFO - 周期性的延时执行Runnable接口 [2846]开始运行 : 1523003712250
2018-04-06 16:35:15 INFO - 周期性的延时执行Runnable接口 [2846]结束运行 : 1523003715251
2018-04-06 16:35:15 INFO - 周期性的延时执行Runnable接口 [2760]开始运行 : 1523003715252
2018-04-06 16:35:18 INFO - 周期性的延时执行Runnable接口 [2760]结束运行 : 1523003718253
2018-04-06 16:35:18 INFO - 周期性的延时执行Runnable接口 [2262]开始运行 : 1523003718253
2018-04-06 16:35:21 INFO - 周期性的延时执行Runnable接口 [2262]结束运行 : 1523003721255
2018-04-06 16:35:21 INFO - 周期性的延时执行Runnable接口 [2565]开始运行 : 1523003721255
2018-04-06 16:35:24 INFO - 周期性的延时执行Runnable接口 [2565]结束运行 : 1523003724256

因为任务的执行时间(3秒)大于开始任务的周期period(2秒),所以:

  • 上一次任务开始 与 下一次任务开始 的间隔时间 = 任务执行时间
  • 上一次任务结束 与 下一次任务开始 的间隔 0 秒

六、总结

以下几个核心组成了Java线程池Executor框架的体系:

  1. Executor接口:是Executor框架的顶级接口,定义了一个用于执行任务的方法execute(Runnable command)
  2. ExecutorService接口:继承自Executor接口,是线程池的主要接口。它扩展了Executor接口,并添加了一些管理线程池的方法,如提交任务、关闭线程池等。
  3. AbstractExecutorService抽象类:实现了ExecutorService接口,提供了一些默认的实现,如线程池管理、任务执行等。
  4. ThreadPoolExecutor类:是ExecutorService接口的主要实现类,也是Java中最常用的线程池实现类。ThreadPoolExecutor使用核心线程池、任务队列和最大线程池来管理和执行任务。
  5. Executors工具类:提供了一些静态工厂方法,用于创建不同类型的线程池,如newFixedThreadPoolnewCachedThreadPool等。
  6. ScheduledExecutorService接口:继承自ExecutorService接口,具有定时任务调度功能。它可以在指定的时间间隔内周期性地执行任务。
  7. Future接口和FutureTask类:用于表示异步计算的结果。可以通过Future对象获取任务的执行结果或取消任务。

它们允许以简单而高效的方式管理和控制并发任务的执行,提高了系统的性能和可伸缩性。在使用线程池时,建议根据具体的应用场景和需求选择合适的线程池类型,并注意正确地管理线程池的生命周期和任务提交。

posted @ 2025-04-27 22:38  夏尔_717  阅读(79)  评论(0)    收藏  举报