线程执行者(二)创建一个线程执行者

简介:

创建一个线程执行者

使用Executor framework的第一步就是创建一个ThreadPoolExecutor类的对象。你可以使用这个类提供的4个构造器或Executors工厂类来 创建ThreadPoolExecutor。一旦有执行者,你就可以提交Runnable或Callable对象给执行者来执行。

在这个指南中,你将会学习如何使用这两种操作来实现一个web服务器的示例,这个web服务器用来处理各种客户端请求。

准备工作

你应该事先阅读第1章中创建和运行线程的指南,了解Java中线程创建的基本机制。你可以比较这两种机制,根据问题选择最好的一个。

这个指南的例子使用Eclipse IDE实现。如果你使用Eclipse或其他IDE,如NetBeans,打开它并创建一个新的Java项目。

如何做…

按以下步骤来实现的这个例子:

1.首先,实现能被服务器执行的任务。创建实现Runnable接口的Task类。


1 public class Task implements Runnable {

2.声明一个类型为Date,名为initDate的属性,来存储任务创建日期,和一个类型为String,名为name的属性,来存储任务的名称。


1 private Date initDate;
2 private String name;

3.实现Task构造器,初始化这两个属性。


1 public Task(String name){
2 initDate=new Date();
3 this.name=name;
4 }

4.实现run()方法。


1 @Override
2 public void run() {

5.首先,将initDate属性和实际日期(这是任务的开始日期)写入到控制台。


1 System.out.printf("%s: Task %s: Created on: %s\n",Thread.currentThread().getName(),name,initDate);
2 System.out.printf("%s: Task %s: Started on: %s\n",Thread.currentThread().getName(),name,new Date());

6.然后,使任务睡眠一个随机时间。


1 try {
2 Long duration=(long)(Math.random()*10);
3 System.out.printf("%s: Task %s: Doing a task during %dseconds\n",Thread.currentThread().getName(),name,duration);
4 TimeUnit.SECONDS.sleep(duration);
5 } catch (InterruptedException e) {
6 e.printStackTrace();
7 }

7.最后,将任务完成时间写入控制台。


1 System.out.printf("%s: Task %s: Finished on: %s\n",Thread.currentThread().getName(),name,new Date());

8.现在,实现服务器类,用来执行使用执行者接受的所有任务。创建一个Server类。


1 public class Server {

9.声明一个类型为ThreadPoolExecutor,名为executor的属性。


1 private ThreadPoolExecutor executor;

10.实现Server构造器,使用Executors类初始化ThreadPoolExecutor对象。


1 public Server(){
2 executor=(ThreadPoolExecutor)Executors.newCachedThreadPool();
3 }

11.实现executeTask()方法,接收Task对象作为参数并将其提交到执行者。首先,写入一条信息到控制台,表明有一个新的任务到达。


1 public void executeTask(Task task){
2 System.out.printf("Server: A new task has arrived\n");

12.然后,调用执行者的execute()方法来提交这个任务。


1 executor.execute(task);

13.最后,将执行者的数据写入到控制台来看它们的状态。


1 System.out.printf("Server: Pool Size: %d\n",executor.getPoolSize());
2 System.out.printf("Server: Active Count: %d\n",executor.getActiveCount());
3 System.out.printf("Server: Completed Tasks: %d\n",executor.getCompletedTaskCount());

14.实现endServer()方法,在这个方法中,调用执行者的shutdown()方法来结束任务执行。


1 public void endServer() {
2 executor.shutdown();
3 }

15.最后,实现这个示例的主类,创建Main类,并实现main()方法。


01 public class Main {
02 public static void main(String[] args) {
03 Server server=new Server();
04 for (int i=0; i<100; i++){
05 Task task=new Task("Task "+i);
06 server.executeTask(task);
07 }
08 server.endServer();
09 }
10 }

它是如何工作的…

Server类是这个示例的关键。它创建和使用ThreadPoolExecutor执行任务。

第一个重要点是在Server类的构造器中创建ThreadPoolExecutor。ThreadPoolExecutor有4个不同的构造器,但由于它 们的复杂性,Java并发API提供Executors类来构造执行者和其他相关对象。即使我们可以通过ThreadPoolExecutor类的任意一 个构造器来创建ThreadPoolExecutor,但这里推荐使用Executors类。

在本例中,你已经使用 newCachedThreadPool()方法创建一个缓存线程池。这个方法返回ExecutorService对象,所以它被转换为 ThreadPoolExecutor类型来访问它的所有方法。你已创建的缓存线程池,当需要执行新的任务会创建新的线程,如果它们已经完成运行任务,变成可用状态,会重新使用这些线程。线程重复利用的好处是,它减少线程创建的时间。缓存线程池的缺点是,为新任务不断创建线程, 所以如果你提交过多的任务给执行者,会使系统超载。

注意事项:使用通过newCachedThreadPool()方法创建的执行者,只有当你有一个合理的线程数或任务有一个很短的执行时间。

一旦你创建执行者,你可以使用execute()方法提交Runnable或Callable类型的任务。在本例中,你提交实现Runnable接口的Task类对象。

你也打印了一些关于执行者信息的日志信息。特别地,你可以使用了以下方法:

  • getPoolSize():此方法返回线程池实际的线程数。
  • getActiveCount():此方法返回在执行者中正在执行任务的线程数。
  • getCompletedTaskCount():此方法返回执行者完成的任务数。

ThreadPoolExecutor 类和一般执行者的一个关键方面是,你必须明确地结束它。如果你没有这么做,这个执行者会继续它的执行,并且这个程序不会结束。如果执行者没有任务可执行, 它会继续等待新任务并且不会结束它的执行。一个Java应用程序将不会结束,除非所有的非守护线程完成它们的执行。所以,如果你不结束这个执行者,你的应用程序将不会结束。

当执行者完成所有待处理的任务,你可以使用ThreadPoolExecutor类的shutdown()方法来表明你想要结束执行者。在你调用shutdown()方法之后,如果你试图提交其他任务给执行者,它将会拒绝,并且抛出RejectedExecutionException异常。

以下截图展示了执行这个示例的一部分:

1

当最后的任务到达服务器时,执行者拥有100个任务,97个活动线程的池。

不止这些…

ThreadPoolExecutor 类提供了许多获取它状态的方法,我们在这个示例中,使用getPoolSize()、getActiveCount()和 getCompletedTaskCount()方法来获取执行者的池大小、线程数、完成任务数信息。你也可以使用 getLargestPoolSize()方法,返回池中某一时刻最大的线程数。

ThreadPoolExecutor类也提供其他与结束执行者相关的方法,这些方法是:

  • shutdownNow():此方法立即关闭执行者。它不会执行待处理的任务,但是它会返回待处理任务的列表。当你调用这个方法时,正在运行的任务继续它们的执行,但这个方法并不会等待它们的结束。
  • isTerminated():如果你已经调用shutdown()或shutdownNow()方法,并且执行者完成关闭它的处理时,此方法返回true。
  • isShutdown():如果你在执行者中调用shutdown()方法,此方法返回true。
  • awaitTermination(long timeout, TimeUnit unit):此方法阻塞调用线程,直到执行者的任务结束或超时。TimeUnit类是个枚举类,有如下常 量:DAYS,HOURS,MICROSECONDS, MILLISECONDS, MINUTES,,NANOSECONDS 和SECONDS。

注意事项:如果你想要等待任务的完成,不管它们的持续时间,请使用大的超时,如:DAYS。

参见

  • 在第4章,线程执行者的中执行者控制被拒绝任务指南
  • 在第8章,测试并发应用程序中的监控Executor framework指南
目录
相关文章
|
Java 存储 开发工具
线程执行者(六)运行多个任务并处理所有结果
声明:本文是《 Java 7 Concurrency Cookbook 》的第四章,作者: Javier Fernández González 译者:许巧辉 校对:方腾飞,叶磊 运行多个任务并处理所有结果 执行者框架允许你在不用担心线程创建和执行的情况下,并发的执行任务。
1227 0
|
Java 开发工具 IDE
线程执行者(七)执行者延迟运行一个任务
声明:本文是《 Java 7 Concurrency Cookbook 》的第四章,作者: Javier Fernández González     译者:许巧辉     校对:方腾飞,叶磊 执行者延迟运行一个任务 执行者框架提供ThreadPoolExecutor类,使用池中的线程来执行Callable和Runnable任务,这样可以避免所有线程的创建操作。
1284 0