Java 并发:Executors 和线程池

让我们开始来从入门了解一下 Java 的并发编程。

本文主要介绍如何开始创建线程以及管理线程池,在 Java 语言中,一个最简单的线程如下代码所示:

Runnable runnable = new Runnable(){
public void run(){
System.out.println("Run");
}
}

可通过下面一行代码来启动这个线程:

new Thread(runnable).start();

这是一个再简单不过的例子了,但如果你有许多需要长时间运行的任务同时执行,并需要等所有的这些线程都执行完毕,还想得到一个返回值,那么这就有点小小难度了。但
Java 已经有解决方案给你,那就是 Executors ,一个简单的类可以让你创建线程池和线程工厂。

一个线程池使用类 ExecutorService 的实例来表示,通过 ExecutorService
你可以提交任务,并进行调度执行。下面列举一些你可以通过 Executors 类来创建的线程池的类型:

  • Single Thread Executor :
    只有一个线程的线程池,因此所有提交的任务是顺序执行,代码:Executors.newSingleThreadExecutor()

  • Cached Thread Pool :
    线程池里有很多线程需要同时执行,老的可用线程将被新的任务触发重新执行,如果线程超过60秒内没执行,那么将被终止并从池中删除,代码:Executors.newCachedThreadPool()

  • Fixed Thread Pool :
    拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待,代码:Executors.newFixedThreadPool()

  • Scheduled Thread Pool :
    用来调度即将执行的任务的线程池,代码:Executors.newScheduledThreadPool()

  • Single Thread Scheduled Pool :
    只有一个线程,用来调度执行将来的任务,代码:Executors.newSingleThreadScheduledExecutor()

一旦你创建了一个线程池,你就可以往池中通过不同的方法提交执行任务,可提交 Runnable 或者 Callable 到线程池中,该方法返回一个
Future 实例表示任务的状态,如果你提交一个 Runnable ,那么如果任务完成后 Future 对象返回 null。

例如,你编写下面的 Callable:

private final class StringTask extends Callable<String>{
public String call(){
//Long operations

return "Run";
}
}


如果你想使用4个线程来执行这个任务10次,那么代码如下:

ExecutorService pool = Executors.newFixedThreadPool(4);

for(int i = 0; i < 10; i++){
pool.submit(new StringTask());
}


但你必须手工的关闭线程池来结束所有池中的线程:

pool.shutdown();

如果你不这么做,JVM 并不会去关闭这些线程;另外你可以使用 shutdownNow()
的方法来强制关闭线程池,那么执行中的线程也会被中断,所有尚未被执行的任务也将不会再执行。

但这个例子中,你无法获取任务的执行状态,因此我们需要借助 Future 对象:

ExecutorService pool = Executors.newFixedThreadPool(4);

List<Future<String>> futures = new ArrayList<Future<String>>(10);

for(int i = 0; i < 10; i++){
futures.add(pool.submit(new StringTask()));
}

for(Future<String> future : futures){
String result = future.get();

//Compute the result
}

pool.shutdown();

不过这段代码稍微有点复杂,而且有不足的地方。如果第一个任务耗费非常长的时间来执行,然后其他的任务都早于它结束,那么当前线程就无法在第一个任务结束之前获得执行结果,但是别着急,Java
为你提供了解决方案——CompletionService。

一个 CompletionService 就是一个服务,用以简化等待任务的执行结果,实现的类是
ExecutorCompletionService,该类基于 ExecutorService,因此我们可试试下面的代码:

ExecutorService threadPool = Executors.newFixedThreadPool(4);
CompletionService<String> pool = new ExecutorCompletionService<String>(threadPool);

for(int i = 0; i < 10; i++){
pool.submit(new StringTask());
}

for(int i = 0; i < 10; i++){
String result = pool.take().get();

//Compute the result
}

threadPool.shutdown();


通过这段代码,我们可以根据执行结束的顺序获取对应的结果,而无需维护一个 Future 对象的集合。

这就是本文的全部,通过 Java 为我们提供的各种工具,可以方便的进行多任务的编程,通过使用 Executors、ExecutorService 以及
CompletionService 等工具类,我们可以创建复杂的并行任务执行算法,而且可以轻松改变线程数。

希望这篇短文能有助于你对并发编程的理解。

时间: 2024-10-06 21:51:06

Java 并发:Executors 和线程池的相关文章

【转】Java并发编程:线程池的使用

Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务? 在Java中可以通过线程池来达到这样的效果.今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPool

Java并发编程:线程池的使用(转)

Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务? 在Java中可以通过线程池来达到这样的效果.今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPool

Java并发基础(六) - 线程池

Java并发基础(六) - 线程池 1. 概述 这里讲一下Java并发编程的线程池的原理及其实现 2. 线程池的基本用法 2.1 线程池的处理流程图 该图来自<Java并发编程的艺术>: 从图中我们可以看出当一个新任务到线程池时,线程池的处理流程如下: 线程池首先判断线程池里面线程数是否达到核心线程数.如果不是则直接创建新线程作为核心线程来执行该任务(该线程作为核心线程不会由于任务的完成而销毁),否则进入下一流程. 判断阻塞队列是否已经满了.如果没满则将该任务放入阻塞队列中,等待核心线程处理,

Java并发编程:线程池的使用

在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务? 在Java中可以通过线程池来达到这样的效果.今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPoolExecutor类中的方法讲起,

Java 并发编程之线程池的使用

在任务与执行策略之间的隐性耦合 Executor框架可以将任务的提交与任务的执行策略解耦开来(就是独立化).虽然Executor框架为制定和修改执行策略都提供了相当大的灵活性,但并非所有的任务都能适用所有的执行策略 比如: 依赖性任务 比如依赖于执行时序,执行结果或者其他效果,那么任务就带有隐含的依赖性.此时必须小心 地维持这些执行策略以避免产生活跃性问题(死锁等造成执行困难的问题) 使用线程封闭机制的任务 与线程池相比,单线程的Executor能够对并发性做出更强的承诺,它们能确保任务不会并发

Java并发编程之线程池

一.概述 在执行并发任务时,我们可以把任务传递给一个线程池,来替代为每个并发执行的任务都启动一个新的线程,只要池里有空闲的线程,任务就会分配一个线程执行.在线程池的内部,任务被插入一个阻塞队列(BlockingQueue),线程池里的线程会去取这个队列里的任务. 利用线程池有三个好处: 降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗 提高响应速度.当任务到达时,任务可以不需要的等到线程创建就能立即执行 提高线程的可管理性.线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,

Java并发编程中线程池源码分析及使用

当Java处理高并发的时候,线程数量特别的多的时候,而且每个线程都是执行很短的时间就结束了,频繁创建线程和销毁线程需要占用很多系统的资源和时间,会降低系统的工作效率. 参考http://www.cnblogs.com/dolphin0520/p/3932921.html 由于原文作者使用的API 是1.6 版本的,参考他的文章,做了一些修改成 jdk 1.8版本的方法,涉及到的内容比较多,可能有少许错误. API : jdk1.8.0_144 ThreadPoolExecutor类 Java中线

Java并发编程:线程池

一.为什么使用线程池 使用线程的时候直接就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 通过使用线程池可以达到这样的效果:空闲下来的线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务. 二.Java中的ThreadPoolExecutor类 首先我们从最核心的ThreadPoolExecutor类中的方法讲起,然

Java 并发编程之线程池的使用 (三)

线程工厂 每当线程池需要创建一个线程时,都是通过线程工厂方法来完善的.默认的线程工厂方法将创建一个新的.非守护的线程,并且不包含特殊的配置信息,通过指定一个线程工厂方法,可以线程池的配置信息. 需要定制线程工厂方法的情景 : 需要为线程池里面的线程指定 个UncaughtExceptionHandler 实例化一个定制的Thread类执行调试信息的记录 需要修改线程的优先级或者守护线程的状态(这建设使用这两个功能,线程优先级会增加平台依赖性,并且导致活跃性问题,在大多数并发应用程序中,都可以使用

Java 并发编程之线程池的使用 (二)

设置线程池的大小 如果线程池过大,那么可能会耗尽资源 ,如果过小,那么 将导致许多空闲的处理器无法工作,从而降低吞吐率. 要设置正确的线程池大小,需要分析计算环境,资源预算和任务的特性,cpu数量,内存大小,任务是计算密集型还是I/O密集型,还是二者皆可.它们是否需要像JDBC连接这样的稀缺资源,下面给出一个计算公式 N(threads)=N(cpu)*U(cpu)*(1+w/c); N(threads)是最后得到的结果大小 . N(cpu)是cpu数量,我的电脑是双核四线程,cpu的数量会是4