Java并发编程实战笔记(5)-任务执行

Posted by zhidaliao on February 4, 2016

相关知识点

  • 大多数并发应用,都是通过任务执行来构造的,通过将任务分成小的工作单元,提供一种自然的事务边界来优化错误恢复过程.
  • 任务执行(Task Execution)的重点在于:独立性明确的任务执行策略
  • 无限制创建线程的不足
    • 线程的创建需要时间,消耗大量的运算资源
    • 活跃的线程消耗系统资源,特别是内存;可运行的线程数量多于CPU的数量将会造成闲置,占用内存,大量线程还会引起竞争
    • 稳定性:JVM和Thread构造函数指定的栈大小,以及系统对线程的限制都会造成崩溃。
  • future可以设置超时时间。

Note: 32位的机器,限制因素主要是线程栈的地址空间,每个线程维护两个执行栈,一个用于Java代码,一个用于原生代码,通常JVM会默认创建一个复合栈,大概是0.5兆(通过 -Xss或者Thread构造函数来设置),2的32次方除以栈大小,会将线程限制在几万内。

Executor

  • 任务是一组逻辑工作单元 , 而线程则是任务异步执行的机制
  • Executor能支持多种不同类型的任务执行策略
  • 提供了一种标准的方法将 任务的提交任务的执行 分开,使用Runnable表示任务
  • 提供了对生命周期统计信息收集程序管理性能监控的支持。
  • 基于生产者-消费者模式,提交任务是生产,执行任务的线程相当于消费者
  • 通过不同的 Executor的实现就能改变程序的行为。
  • Java类库中,任务执行的主要抽象不是 Thread,而是 Executor
interface Executor{
	void execute(Runnable command);
}
class Demo{
	private final Executor exec = Executors.newFixedThreadPool(100);
	
	void test(){
		// 运行
		Runnable task = new Runnable(){
			public void run(){
				//...
			}
		}
		// 提交
		exec.execute(task);
		
	}
}	
线程池

通过管理一组工作线程的资源池,能够减少创建和销毁的开销(资源层面),并且不需要等待,提高响应性 Executor静态工厂方法提供以下几种资源池:

  • newFixedThreadPool: 固定大小,如果线程异常结束会补充
  • newCachedThreadPool: 可缓存线程池,少了创建,多了回收
  • newSingleThreadExecutor: 单线程,会按照队列中的顺序(可设置)串行执行
  • newScheduledThreadPool: 固定大小,能延迟或者定时执行
Executor生命周期

Executor实现通常创建线程执行,JVM只有在所有的非守护线程结束才能关闭, 类库提供了 基于 Executor的 ExecutorService接口

  • shutdown: 不接受新任务,等待开始的任务结束

  • shutdownNow: 尝试取消所有开始的任务,不再启动尚未开始(可能是已接受的新任务)执行的任务。

interface ExecutorService extends Executor{
	void shutdown();
	List<Runnable> shutdownNow();
	boolean isShutdown();
	...//
}

生命周期三种状态 运行、关闭、已终止

  • ExecutorService关闭后,提交新的任务会抛出 RejectExecutionException
  • 所有任务完成会变成已终止状态
  • awaitTermination会等待 Executor到达终止状态,并且会立即调用shutdown方法。
  • ExecutorService所有submit方法,都会返回一个Future
Timer和ScheduledThreadPoolExecutor

Timer负责管理 延迟任务 和 周期任务;缺陷如下:

  • 只会创建一个线程,如果任务耗时过长会影响其他任务
  • Timer线程不捕获异常, TimerTask抛出异常整个Timer任务都会取消。

如果要构建自己的调度器,可以使用DelayQueue和ScheduledThreadPoolExecutor组合。

异构任务
  • 定义: 不同类型的任务平均分配给每个消费者。当消费者增加,在分配工作和协同工作的时候会存在考验。
  • 只有大量相互独立 并且 同构的任务并发处理才能最大限度的提升性能
ExecutorCompletionService
  • 一组Future任务,如果需要知道哪些完成了,需要不停的循环每个Future的Get方法,非常繁琐。
  • CompletionService 将 Executor 和BlockingQueue 功能融合在一起
  • 多个 ExecutorCompletionService 可以共享一个 Executor,
class Demo{
	final ExecutorService executor;

	void test(){
		CompletionService cs = new ExecutorCompletionService(executor); 
		
		for(...){
			cs.submit(new Callable(){
				//...
			});
		}

		for(...){
			// 返回的都是已经计算好结果的Callable
			Future<T> future = cs.take();
			future.get();
		}
	}
}