并发程序的构建 java
大多数的并发程序都是经过“任务执行”来构造的,任务一般是一些抽象且离散的工做单元。将业务逻辑抽象城一个个的任务,交给不一样线程来并发执行。java中能够经过Runnable来定义任务单元,经过Thread以独立的线程执行。线程是比较宝贵的资源,须要合理的复用、管理、分配、执行。线程池管理线程使用的一个组件。安全
线程池 并发
jdk的线程池使用很是简单,经过Executor的excute方法执行任务便可,至于线程如何管理取决于Executor的实现。Executor经过生产者-消费者模式,将任务和执行策略进行了解耦。spa
public interface Executor { void execute(Runnable command); }
jdk提供了ThreadPoolExecutor做为Executor的线程池实现。该实现线程池中有几个重要的概念:固定线程数(核心线程数)、最大线程数、队列大小、线程存活时间。固定线程数建立后就不会消亡;队列是超过固定线程执行能力的任务暂存于队列;最大限线程数是超过固定线程及队列能力的提供动态扩展线程能力,在使用完后超过指定存活时间没有被使用就会被消亡。经过这几个核心变量来建立不一样的线程池。线程
执行流程:当提交任务给线程池时,首先会判断当前线程个数是否小于核心线程池数,小于直接建立线程执行,不然将任务放入队列;若是队列也满了,那么就会看当前线程是否小于最大线程数,小于直接建立线程执行。线程池会不停从队列中取任务,超过核心线程数的线程,在存活时间后,会自动回收。code
jdk提供了一些线程池和默认的配置,能够经过Executors中的静态工厂方法来建立线程池。对象
方法 | 说明 |
newFixedThreadPool | 建立一个固定长度的线程池,固定线程数n;最大线程数n;LinkedBlockingQueue队列 |
newCachedThreadPool | 建立一个可扩展的线程池,固定线程数0;最大线程数Integer.MAX_VALUE;存活60秒;SynchronousQueue队列 |
newSingleThreadExecutor | 建立一个单线程的线程池,固定线程数1;最大线程数1;LinkedBlockingQueue队列 |
newScheduledThreadPool | 建立一个固定长度的线程池,而且以延时或定时执行 |
为了解决执行服务的生命周期问题,Executor扩展了ExecutorService接口,而且提供了一些生命周期管理的方法。ExecutorService有3种状态,运行、关闭、终止。在初始建立的时候,处于运行状态;当调用shutdown后,再也不接受新任务,已提交的任务会执行完,此时处于关闭状态;当任务执行完,则处于终止状态。blog
shutdownNow与shutdown的不一样在于,shutdownNow不会等待已提交的任务执行完,而是当即尝试中止执行的任务和在队列中的任务,而且返回等待中的任务。接口
awaitTermination会执行shutdown而且阻塞,直处处于终止状态。生命周期
Callable与Future
Executor使用Runnable做为其任务表示时,有一个缺陷就是Runnable没有返回值而且不能抛出受检查的异常。这使得咱们须要经过一些共享资源来同步线程执行结果。
Callable多是一个对任务更全面的定义。
public interface Callable<V> { V call() throws Exception; }
ExecutorService一样扩展了获取返回值和执行异常的能力而且提供了取消任务的能力, <T> Future<T> submit(Callable<T> task)方法提交一个任务,并返回一个Future。本质上Future的实现就是一个共享对象,线程执行完本身的任务后会将结果设置在Future的实现中,而且进行了并发控制,实现线程安全。
Future的get方法会一直阻塞直到任务完成,返回结果;若是执行抛出异常,则会将异常封装成ExecutionException从新抛出;若是任务被取消,则会抛出CancellationException;cancel(boolean mayInterruptIfRunning)尝试取消已提交的任务,若是任务还未执行,则任务将被取消并返回true,若是任务已经执行,根据mayInterruptIfRunning尝试中断。已经执行或者已经取消的任务将返回false。
此外ExecutorService提供了批量执行的方法,invokeAll提交一个Callable集合,并按顺序返回Future集合。invokeAny提交一个Callable集合,任意一个任务执行完成即返回。
线程池的管理
线程池的各项配置依赖与任务自己(任务运行时长,cpu使用率等)以及任务依赖的各项资源(cpu、内存、依赖任务等),(从线程的角度来看,线程仍是串行执行任务的),最终的目的还说让cpu达到最高的利用率,而且减小任务切换带来的额外损耗。