并发编程从入门到放弃系列开始和结束(六)
提交任务
重点说一下几个方法:
submit(Runnable task, T result):可以用于主线程和子线程之间的通信,数据共享。
submit(Runnable task):返回null,相当于调用submit(Runnable task, null)。
invokeAll(Collection<? extends Callable> tasks):批量提交任务,阻塞等待所有任务执行完成之后返回,带超时时间的则是在超时之后返回,并且取消没有执行完成的任务。
invokeAny(Collection<? extends Callable> tasks):批量提交任务,只要一个任务有返回,那么其他的任务都会被终止。
public void execute(Runnable command); //提交runnable任务,无返回
public <T> Future<T> submit(Callable<T> task); //提交callable任务,有返回
public Future<?> submit(Runnable task); //提交runnable,有返回
public <T> Future<T> submit(Runnable task, T result); //提交runnable,有返回
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks); //批量提交任务
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit);
public <T> T invokeAny(Collection<? extends Callable<T>> tasks);
public <T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit);
关闭
shutdown:线程池状态设置为SHUTDOWN,不再接受新任务,直接返回,线程池中任务会执行完成,遍历线程池中的线程,逐个调用interrupt方法去中断线程。
shutdownNow:线程池状态设置为STOP,不再接受新任务,直接返回,线程池中任务会被中断,返回值为被丢弃的任务列表。
isShutdown:只要调用了shutdown或者shutdownNow,都会返回true
isTerminating:所有任务都关闭后,才返回true
public void shutdown();
public List<Runnable> shutdownNow();
public boolean isShutdown();
public boolean isTerminating();
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor 继承于 ThreadPoolExecutor,从名字我们也知道,他是用于定时执行任务的线程池。
内部实现了一个DelayedWorkQueue作为任务的阻塞队列,ScheduledFutureTask 作为调度的任务,保存到队列中。
我们先看下他的构造函数,4个构造函数都不支持传队列进来,所以默认的就是使用他的内部类 DelayedWorkQueue,由于 DelayedWorkQueue 是一个无界队列,所以这里最大线程数都是设置的为 Integer.MAX,因为没有意义。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), handler);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
执行定时任务的方法主要有4个,前面两个 schedule 传参区分 Runnable 和 Callable 其实并没有区别,最终 Runnable 会通过 Executors.callable(runnable, result) 转换为 Callable,本质上我们可以当做只有3个执行方法来看。
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay,
TimeUnit unit);
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
schedule:提交一个延时任务,从时间单位为 unit 的 delay 时间开始执行,并且任务只会执行一次。
scheduleWithFixedDelay:以固定的延迟时间重复执行任务,initialDelay 表示提交任务后多长时间开始执行,delay 表示任务执行时间间隔。
scheduleAtFixedRate:以固定的时间频率重复执行任务,指的是以起始时间开始,然后以固定的时间间隔重复执行任务,initialDelay 表示提交任务后多长时间开始执行,然后从 initialDelay + N * period执行。
这两个特别容易搞混,很难理解到底是个啥意思,记住了。
scheduleAtFixedRate 是上次执行完成之后立刻执行,scheduleWithFixedDelay 则是上次执行完成+delay 后执行。
看个例子,两个任务都会延迟1秒,然后以2秒的间隔开始重复执行,任务睡眠1秒的时间。
scheduleAtFixedRate 由于任务执行的耗时比时间间隔小,所以始终是以2秒的间隔在执行。
scheduleWithFixedDelay 因为任务耗时用了1秒,导致后面的时间间隔都成了3秒。
public class ScheduledThreadPoolTest {
public static void main(String[] args) throws Exception {
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(10);
executorService.scheduleAtFixedRate(() -> {
try {
System.out.println("scheduleAtFixedRate=" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, 1000, 2000, TimeUnit.MILLISECONDS);
executorService.scheduleWithFixedDelay(() -> {
try {
System.err.println("scheduleWithFixedDelay=" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, 1000, 2000, TimeUnit.MILLISECONDS);
// executorService.shutdown();
}
}
//输出
scheduleAtFixedRate=01:17:05
scheduleWithFixedDelay=01:17:05
scheduleAtFixedRate=01:17:07
scheduleWithFixedDelay=01:17:08
scheduleAtFixedRate=01:17:09
scheduleAtFixedRate=01:17:11
scheduleWithFixedDelay=01:17:11
scheduleAtFixedRate=01:17:13
scheduleWithFixedDelay=01:17:14
scheduleAtFixedRate=01:17:15
scheduleAtFixedRate=01:17:17
scheduleWithFixedDelay=01:17:17
scheduleAtFixedRate=01:17:19
scheduleWithFixedDelay=01:17:20
scheduleAtFixedRate=01:17:21
我们把任务耗时调整到超过时间间隔,比如改成睡眠3秒,观察输出结果。
scheduleAtFixedRate 由于任务执行的耗时比时间间隔长,按照规定上次任务执行结束之后立刻执行,所以变成以3秒的时间间隔执行。
scheduleWithFixedDelay 因为任务耗时用了3秒,导致后面的时间间隔都成了5秒。
scheduleWithFixedDelay=01:46:21
scheduleAtFixedRate=01:46:21
scheduleAtFixedRate=01:46:24
scheduleWithFixedDelay=01:46:26
scheduleAtFixedRate=01:46:27
scheduleAtFixedRate=01:46:30
scheduleWithFixedDelay=01:46:31
scheduleAtFixedRate=01:46:33
scheduleWithFixedDelay=01:46:36
scheduleAtFixedRate=01:46:36
OK,最后来说说实现原理:
- 首先我们通过调用 schedule 的几个方法,把任务添加到 ScheduledThreadPoolExecutor 去执行
- 接收到任务之后,会通过请求参数的延迟时间计算出真正需要执行任务的时间,然后把任务封装成 RunnableScheduledFuture
- 然后把封装之后的任务添加到延迟队列中,任务 ScheduledFutureTask 实现了 comparable 接口,把时间越小的任务放在队列头,如果时间一样,则会通过
- sequenceNumber 去比较,也就是执行时间相同,先提交的先执行
- 最后线程池会从延迟队列中去获取任务执行,如果是一次性的任务,执行之后删除队列中的任务,如果是重复执行的,则再次计算时间,然后把任务添加到延迟队列中
CompletionService
记得上面我将 ThreadPoolExecutor 的方法吗,其中有一个 invokeAny 的方法,批量提交任务,只要有一个完成了,就直接返回,而不用一直傻傻地等,他的实现就是使用了 CompletionService ,我给你看一段源码。
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<T> ecs = new ExecutorCompletionService<T>(this);
}
看到了吧,OK,在我们想试试使用这个类之前,我们先试试 invokeAny 好使不。
public class CompletionServiceTest {
private static final int TOTAL = 10;
private static ExecutorService executorService = Executors.newFixedThreadPool(TOTAL);
public static void main(String[] args) throws Exception {
testInvokeAny();
}
private static void testInvokeAny() throws Exception {
List<TestTask> taskList = new LinkedList<>();
for (int i = 0; i < TOTAL; i++) {
taskList.add(new TestTask(i));
}
String value = executorService.invokeAny(taskList, 60, TimeUnit.SECONDS);
System.out.println("get value = " + value);
executorService.shutdown();
}
static class TestTask implements Callable<String> {
private Integer index;
public TestTask(Integer index) {
this.index = index;
}
@Override
public String call() throws Exception {
long sleepTime = ThreadLocalRandom.current().nextInt(1000, 10000);
System.out.println("task-" + index + " sleep " + sleepTime + " Ms");
Thread.sleep(sleepTime);
return "task-" + index;
}
}
}
//输出
task-7 sleep 3072 Ms
task-4 sleep 1186 Ms
task-3 sleep 6182 Ms
task-9 sleep 7411 Ms
task-0 sleep 1882 Ms
task-1 sleep 8274 Ms
task-2 sleep 4789 Ms
task-5 sleep 8894 Ms
task-8 sleep 7211 Ms
task-6 sleep 5959 Ms
get value = task-4
看到效果了吧,耗时最短的任务返回,整个流程就结束了,那我们试试自己用 CompletionService 来实现这个效果看看。
文章转自公众号:艾小仙