在Java 5之前,在使用Java并发API实现并发应用时,需要自己管理线程。首先实现Runnable接口或者继承Thread类,然后创建thread对象并使用其start()方法开始执行。我们还需要控制线程状态,来了解线程是否已经结束执行或者依然在运行。
Java版本5中,出现了作为执行线程池提供者的执行器概念。这种机制,通过Executor和ExecutorService接口以及ThreadPoolExecutor和ScheduledThreadPoolExecutor类实现,我们只专注于任务逻辑实现即可。通过实现任务并发送到执行器,执行器中的线程池用来创建、管理和结束线程。在Java版本7中,fork/join框架提供了另一种执行器机制的实现,专门处理可分解为更小的子问题的问题。这种方法有许多优点,如下所示:
- 不需要创建所有任务的线程。当发送任务到执行器且被池中的线程执行时,我们节省了创建新线程的时间。如果应用需要执行很多任务,节省的总时间将非常显著且应用性能将更好。
- 如果我们创建少量的线程,应用也将使用很少内存,同样性能会更好。
- 通过实现Runnable或Callable接口来构建在执行器中执行的并发任务。Callable接口实现返回结果的任务,这远优于传统任务。
- 当发送任务到执行器时,返回Future对象了解任务状态和返回结果,确保是否已经完成执行。
- 通过ScheduledThreadPoolExecutor类实现的特殊执行器,我们能够调度任务,并重复执行它们。
- 通过执行器能够轻松地控制资源。我们能够建立池中线程的最大数量,这样执行器每次运行的任务数量永远不会超过最大数量。
与直接使用线程相比,使用执行器有很多优势。本节将实现范例,展示如何使用执行器比单独创建线程得到更好的性能。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤实现范例:
-
创建名为Task的类,指定其实现Runnable接口:
public class Task implements Runnable {
-
实现run()方法,创建重复1000000次的循环,每次进行一些整数的数学操作:
@Override public void run() { int r; for (int i=0; i<1000000; i++) { r=0; r++; r++; r*=r; } } }
-
实现本范例主类,创建名为Main的类,包含main()方法:
public class Main { public static void main(String[] args) {
-
创建执行1000个任务的1000个线程,并等待执行结束,控制总执行时间:
Thread threads[]=new Thread[1000]; Date start,end; start=new Date(); for (int i=0; i<threads.length; i++) { Task task=new Task(); threads[i]=new Thread(task); threads[i].start(); } for (int i=0; i<threads.length; i++) { try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } end=new Date(); System.out.printf("Main: Threads: %d\n", (end.getTime()-start.getTime()));
-
创建Executor对象,发送1000个任务,等待任务结束。计算总执行时间:
ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newCachedThreadPool(); start=new Date(); for (int i=0; i<threads.length; i++) { Task task=new Task(); executor.execute(task); } executor.shutdown(); try { executor.awaitTermination(1, TimeUnit.DAYS); } catch (InterruptedException e) { e.printStackTrace(); } end=new Date(); System.out.printf("Main: Executor: %d\n", (end.getTime()-start.getTime())); } }
工作原理
范例整个执行过程中,使用执行器的执行时间始终比直接创建线程要短。如果应用中有大量任务的话,最好使用执行器。
更多关注
- 本章“使用fork/join框架代替执行器”小节