首页 > 代码库 > 《Java并发编程实战》(五)---- 任务执行
《Java并发编程实战》(五)---- 任务执行
一,在线程中执行任务
1,无限创建线程的不足:
- 线程生命周期的开销非常高。线程的创建过程需要时间,这就延迟了请求的处理,并且需要JVM和操作系统提供一些辅助操作。
- 资源消耗。如果可运行线程数量多于可用处理器的数量,那么有些线程会闲置就会占用许多内存,如果大量线程在竞争CPU还会产生其他的性能消耗。
- 稳定性。在可创建线程的数量上有一个阈值,这个阈值随着平台不同而不同,并且受多个因素制约,包括JVM的启动参数、Thread构造函数中请求的栈大小,以及底层操作系统对线程的限制等。如果超过这个限制,就很可能有OOM异常。
在一定的范围内,增加线程可以提高系统的吞吐量,但如果超过这个范围,再创建更多的线程只会降低程序的执行速度,如果过多地创建线程,整个系统就有可能崩溃。要想避免这种危险,就应该对应用程序可以创建的线程数量进行限制,并且全面地测试应用程序,从而确保线程数量达到限制时,程序也不会耗尽资源。
2,Executor框架
串行执行的问题在于其糟糕的响应性和吞吐量,而“为每个任务都分配一个线程”的问题在于资源管理的复杂性。所以就有了线程池,线程池简化了线程的管理工作,并且java.util.concurrent提供了一种灵活的线程池作为Executor框架的一部分。在java类库中,任务执行的不是Thread,而是Executor:
public interface Executor { void execute(Runnable command); }
1,基于Executor的Web服务器
class TaskExecutorWebServer { private static final int NTHREAD = 100; private static final Executor exe = Executors.newFixedThreadPool(NTHREAD); public static void main(String[] args) { ServerSocket socket = new ServerSocket(80); while(true) { final Socket connection = socket.accept(); Runnable task = new Runnable() { public void run() { handleRequest(connection); } }; exec.execute(task); } } }
在TaskExecutionWebServer中,通过使用Executor,将请求处理任务的提交与任务的实际执行解耦开来。
也可以很容易地将上例修改为ThreadPerTaskWebServer的行为,只需要使用一个为每个请求都创建新线程的Executor。
public class ThreadPerTaskExecutor implements Executor { public void execute(Runnable r) { new Thread(r).start(); } }
还可以编写一个Executor使ThreadPerTaskExecutor的行为类似于单线程的行为:
public class WithinThreadExecutor implements Executor { public void execute(Runnable r) { r.run(); } }
***************************************************
每当看到下面形式的代码时,并且希望获得一种更灵活的执行策略时,考虑使用Executor来代替Thread:
new Thread(runnable).start();
***************************************************
3, 线程池
线程池是指管理一组相同工作线程的资源池。线程池是与工作队列密切相关的,其中在工作队列中保存了所有等待执行的任务。工作线程的任务很简单: 从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。
“在线程池中执行任务”比“为每个任务分配一个线程”优势更多。通过重用现有的线程而不是创建新线程,可以减少在线程创建与销毁的开销。另一个好处是请求到来时,不会再因为要等待线程创建而延迟,也就提高了响应性。通过适当调整线程池的大小,可以创建足够多的线程以便处理器保持忙碌状态,同时还可以防止过多线程互相竞争资源而使应用程序耗尽内存。
类库提供了一个灵活的线程池以及一些有用的默认配置。可以通过调用Executors中的静态工厂方法之一来创建一个线程池:
- newFixedThreadPool:创建一个固定长度的线程池,每当提交一个任务时就创建一个线程,直到达到线程池的最大数量,这时线程池的规模将不再变化。
- newCachedThreadPool:创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程,而当需求增加时,则可以添加新的线程,线程池的规模不存在任何限制。
- newSingleThreadExecutor:是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。它能确保依照任务在队列中的顺序来串行执行。
- newScheduledThreadPool:创建了一个固定长度的线程池,而且以延迟或定时的方式执行任务,类似于Timer。
4,Executor的生命周期
由于Executor以异步方式来执行任务,因此在任何时刻,之前提交任务的状态不是立即可见的。有些任务可能已经完成,有些可能正在运行,而其他的任务可能在队列中等待执行。当关闭应用程序时,可能采用平缓的方式(完成所有已经启动的任务,并且不再接受任何新的任务),也可能采用粗暴方式(直接所有都关掉)。Executor视为应用程序提供服务的,因此它们也是可关闭的,并把在关闭操作中受影响的任务的状态返回给应用程序。
为了解决执行任务的生命周期问题,ExecutorService接口扩展了Executor,添加了一些用于生命周期管理的方法:
public interface ExecutorService extends Executor { void shutdown(); List<Runnable> shutDownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; ... }
ExectuorService的生命周期有三种状态:
《Java并发编程实战》(五)---- 任务执行