执行者框架
创建一个或两个线程并运行它们很容易,但是当您的应用程序需要创建20个或30个线程来同时运行任务时,这将成为一个问题。
同样,毫不夸张地说大型多线程应用程序将同时运行数百个(即使不是数千个)线程。因此,将线程的创建和管理与应用程序的其余部分分开是有意义的。
输入执行程序,这是一个用于创建和管理线程的框架。执行器框架可帮助您-
-
线程创建:它提供了各种创建线程的方法,更具体地说是线程池,您的应用程序可以使用这些方法并发运行任务。
-
线程管理:它管理线程池中线程的生命周期。在提交执行任务之前,您不必担心线程池中的线程是活动的还是繁忙的或死掉的。
-
任务提交和执行:执行程序框架提供了在线程池中提交要执行的任务的方法,还使您能够决定何时执行任务。例如,您可以提交要立即执行的任务,也可以计划将其稍后执行或定期执行。
Java Concurrency API定义了以下三个执行程序接口,这些接口涵盖了创建和管理线程所需的一切-
-
Executor-一个简单的接口,其中包含一种称为
execute()
启动Runnable
对象指定任务的方法的方法。 -
ExecutorService-的子接口
Executor
可添加功能来管理任务的生命周期。它还提供了一种submit()
方法,其重载版本可以接受Runnable
和Callable
对象。可调用对象与可运行对象类似,不同之处在于可调用对象指定的任务也可以返回值。在下一篇博客文章中,我们将更详细地了解Callable 。 -
ScheduledExecutorService-的子接口
ExecutorService
。它增加了计划任务执行的功能。
除了上述三个接口之外,API还提供了一个Executors类,其中包含用于创建不同类型的执行程序服务的工厂方法。
ExecutorService示例
好吧!让我们现在举一个例子来更好地理解事情。在下面的示例中,我们首先使用单个工作线程创建一个ExecutorService,然后提交要在工作线程中执行的任务。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorExample {
public static void main(String[] args) {
System.out.println("Inside : " + Thread.currentThread().getName());
System.out.println("Creating Executor Service...");
ExecutorService executorService = Executors.newSingleThreadExecutor();
System.out.println("Creating a Runnable...");
Runnable runnable = () -> {
System.out.println("Inside : " + Thread.currentThread().getName());
};
System.out.println("Submit the task specified by the runnable to the executor service.");
executorService.submit(runnable);
System.out.println("Shutting down the executor");
executorService.shutdown();
}
}
# Output
Inside : main
Creating Executor Service...
Creating a Runnable...
Submit the task specified by the runnable to the executor service.
Inside : pool-1-thread-1
上面的示例显示了如何创建执行程序服务并在执行程序内部执行任务。我们使用该Executors.newSingleThreadExecutor()
方法创建一个ExecutorService
使用单个工作线程执行任务的。如果提交了一个任务以供执行,并且线程当前正在忙于执行另一个任务,则新任务将在队列中等待,直到线程有空执行它为止。
如果运行上面的程序,您会注意到该程序永远不会退出,因为执行程序服务一直在监听新任务,直到我们明确关闭它为止。
关闭ExecutorService
ExecutorService提供了两种关闭执行器的方法-
-
shutdown() -在
shutdown()
执行程序服务上调用方法时,它将停止接受新任务,等待先前提交的任务执行,然后终止执行程序。 -
shutdownNow() -此方法中断正在运行的任务并立即关闭执行程序。
让我们在程序末尾添加关闭代码,以使其正常退出
具有多个线程和任务的ExecutorService示例
在前面的示例中,我们创建了一个使用单个工作线程的ExecutorService。但是,当我们创建线程池并在线程池中并发执行多个任务时,ExecutorService的真正功能才会发挥作用。
以下示例显示了如何创建使用线程池并同时执行多个任务的执行程序服务-
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class FixedThreadPoolExample {
public static void main(String[] args) {
System.out.println("Inside : " + Thread.currentThread().getName());
System.out.println("Creating Executor Service with a thread pool of Size 2");
ExecutorService executorService = Executors.newFixedThreadPool(2);
Runnable task1 = () -> {
System.out.println("Executing Task1 inside : " + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException ex) {
throw new IllegalStateException(ex);
}
};
Runnable task2 = () -> {
System.out.println("Executing Task2 inside : " + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException ex) {
throw new IllegalStateException(ex);
}
};
Runnable task3 = () -> {
System.out.println("Executing Task3 inside : " + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException ex) {
throw new IllegalStateException(ex);
}
};
System.out.println("Submitting the tasks for execution...");
executorService.submit(task1);
executorService.submit(task2);
executorService.submit(task3);
executorService.shutdown();
}
}
# Output
Inside : main
Creating Executor Service with a thread pool of Size 2
Submitting the tasks for execution...
Executing Task2 inside : pool-1-thread-2
Executing Task1 inside : pool-1-thread-1
Executing Task3 inside : pool-1-thread-1
在上面的示例中,我们创建了一个大小为2的固定线程池的executor服务。固定线程池是线程池中非常常见的一种类型,经常在多线程应用程序中使用。
在固定线程池中,执行程序服务确保该池始终具有指定数量的正在运行的线程。如果任何线程由于某种原因而死,则立即用新线程替换它。
提交新任务时,执行程序服务从池中选择一个可用线程,然后在该线程上执行任务。如果我们提交的任务多于可用线程数,并且所有线程当前都在忙于执行现有任务,那么新任务将等待轮到队列。
线程池
大多数执行器实现使用线程池执行任务。线程池不过是与Runnable
或Callable
任务分开存在的一堆工作线程,由执行程序管理。
创建线程是一项昂贵的操作,应将其最小化。拥有工作线程可以最大程度地减少由于线程创建而造成的开销,因为执行程序服务只需要创建一次线程池,然后它就可以重用线程来执行任何任务。
在上一节中,我们已经看到了一个线程池示例,称为固定线程池。
任务通过称为“阻塞队列”的内部队列提交到线程池。如果任务多于活动线程数,则将它们插入阻塞队列以等待直到任何线程可用。如果阻塞队列已满,则拒绝新任务。
ScheduledExecutorService示例
ScheduledExecutorService用于定期或在指定的延迟后执行任务。
在下面的示例中,我们计划在5秒的延迟后执行任务-
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorsExample {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
Runnable task = () -> {
System.out.println("Executing Task At " + System.nanoTime());
};
System.out.println("Submitting task at " + System.nanoTime() + " to be executed after 5 seconds.");
scheduledExecutorService.schedule(task, 5, TimeUnit.SECONDS);
scheduledExecutorService.shutdown();
}
}
# Output
Submitting task at 2909896838099 to be executed after 5 seconds.
Executing Task At 2914898174612
scheduledExecutorService.schedule()
函数采用Runnable
,延迟值和延迟单位。上述程序在提交后的5秒钟后执行任务。
现在来看一个示例,其中我们定期执行任务-
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorsPeriodicExample {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
Runnable task = () -> {
System.out.println("Executing Task At " + System.nanoTime());
};
System.out.println("scheduling task to be executed every 2 seconds with an initial delay of 0 seconds");
scheduledExecutorService.scheduleAtFixedRate(task, 0, 2, TimeUnit.SECONDS);
}
}
# Output
scheduling task to be executed every 2 seconds with an initial delay of 0 seconds
Executing Task At 2996678636683
Executing Task At 2998680789041
Executing Task At 3000679706326
Executing Task At 3002679224212
.....
scheduledExecutorService.scheduleAtFixedRate()
方法采用Runnable
,初始延迟,执行时间和时间单位。在指定的延迟后,它将开始执行给定的任务,然后以周期值指定的时间间隔定期执行该任务。
请注意,如果任务遇到异常,将禁止任务的后续执行。否则,仅当您关闭执行程序或杀死程序时,任务才会终止。
结论
在此博客文章中,我们学习了执行程序和线程池的基础。