Java并发编程实战笔记4:线程池

在生产环境中,为每一个任务分配一个线程存在一些缺陷,尤为是当须要建立大量线程时。java

  • 线程生命周期的开销很是高。线程的建立和销毁都是有开销的。
  • 资源消耗。活跃的线程会消耗系统资源,尤为是内存。
  • 稳定性。在可建立线程的数量上存在一个限制。若是破坏了这个限制,极可能抛出OutOfMemoryError异常。

也就是说,在必定范围内,增长线程能够提升系统的吞吐率,可是超过了这个范围,再建立更多的线程只会下降程序的执行速度。web

在《阿里巴巴Java手册》中提到: 编程

线程池是指管理一组同构工做线程的资源池。线程从工做队列中获取一个任务,执行任务,而后返回线程等待下一个任务。缓存

在线程池中执行任务比为每个任务分配一个线程优点更多。经过重用现有的线程而不是建立新线程,能够分摊在线程建立和销毁过程当中产生的巨大开销。当请求到达时,工做线程一般已经存在,所以不会因为等待建立线程延迟任务执行,提升了响应性。经过适当调整线程池的大小,能够建立足够多的线程使得处理器保持忙绿,同时还能够防止过多线程互相竞争使得内存耗尽。服务器

所以应该使用线程池。其目的包括:多线程

  • 线程是稀缺资源,不能频繁建立
  • 线程的建立和执行彻底分开,方便维护,进行了解耦
  • 将线程放入池中,能够给其余任务复用

Executor框架

Executor框架是在java.util.concurrent包下,在Java 5中引入的。经过Executor来启动线程比使用Thread.start()方法更好,更易于管理且效率更高(用线程池实现节约了开销),而且还有助于避免this逸出。并发

Executor两级调度模型以下。用户将多个任务提交给Executor框架,框架在线程池中分配线程执行它们。而后操做系统再将这些线程分配给处理器执行。 框架

Executor框架结构图:
框架中的全部类能够分为三类:

  1. 任务:Runnable和Callable
  2. 任务执行器:Executor,其子接口为ExecutorService,该子接口的实现类有:ThreadPoolExecutor和ScheduledThreadPoolExecutor
  3. 执行结果:使用Future接口表示异步的执行结果,其实现类为FutureTask。

Executor

Executor是个简单的接口,它支持多种不一样类型的任务执行策略,提供了一种标准的方法将任务的提交过程与执行过程解耦,并用Runnable表示任务。Executor的实现还提供了对生命周期的支持。异步

Executor接口中之定义了一个方法execute(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类。socket

Executor基于生产者-消费者模式,提交任务的操做至关于生产者,执行任务的线程至关于消费者。

class TaskExecWebServer{
    private static final int NTHREADS=100;
    private static final Executor executor=Executors.newFixedThreadPool(NTHREADS);

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket=new ServerSocket(8080);
        while(true){
            final Socket socket=serverSocket.accept();
            Runnable task=new Runnable() {
                @Override
                public void run() {
                    System.out.println(socket);
                }
            };
            executor.execute(task);
        }
    }
}

class ThreadPerTaskExecutor implements Executor{
    @Override
    public void execute(Runnable command) {
        new Thread(command).start();
    }
}

class WithinThreadExecutor implements Executor{
    @Override
    public void execute(Runnable command) {
        command.run();
    }
}
复制代码

在上述代码中,咱们首先在webServer中使用了一个固定大小为100的线程池。而后使用executor.execute(task)来提交线程。在执行的过程当中,能够采用不一样的执行方法。能够为每一个请求启动一个新的线程,如ThreadPerTaskExecutor,还能够以同步方式执行全部任务,如WithinThreadExecutor。这样就实现了把任务的提交和执行解耦。

ThreadPoolExecutor

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) 
复制代码
  • corePoolSize是线程池的基本大小。
  • maximumPoolSize是线程池的最大线程大小。
  • keepAliveTimeunit是线程空闲后的存活时间
  • workQueue是用于存听任务的阻塞队列
  • handler是当队列和最大线程池都满了以后的饱和策略

execute()

  1. 首先获取线程池的状态
  2. 若是线程池中线程数量少于corePoolSize,即便线程池中有空闲线程,也会建立一个新线程来执行新添加的任务。
  3. 若是当前线程处于运行状态,而且成功的写入了阻塞队列
  4. 双重检查,再次获取线程状态;若是线程不是运行状态就须要从阻塞队列移除,并尝试判断线程是否执行完毕。同时执行拒绝策略。
  5. 若是当前线程池为空就新建立一个线程并执行。
  6. 若是在第三部的判断为非运行状态,建立新线程,失败则执行拒绝策略。

生命周期

为了解决执行服务的生命周期问题,Executor拓展了ExecutorService接口,添加了一些用于生命周期管理的方法。ExecutorService的生命周期有三种状态:运行、关闭和已终止。

在初始建立时,ExecutorService处于运行状态。使用shutdown()方法将再也不接受新的任务,同时等待提交的任务都执行完成以后再关闭。调用shutdownNow()方法,至关于调用了每一个线程的interrupt()方法,将尝试取消全部运行中的任务,而且再也不启动队列中还没有开始执行的任务。

在ThreadPoolExecutor中,定义了以下状态:

  • RUNNING 是运行状态,能够接受执行队列中的任务
  • SHOTDOWN 是调用了shutdown()方法
  • STOP 是调用了shutdownNow()方法
  • TIDYING 是全部任务都执行完毕的状态,在调用shutdown()或shutdownNow()时都会尝试更新为这个状态
  • TERMINATED 是终止状态,当执行terminated()会更新为这个状态

线程池

Executors提供了一系列工厂方法用于建立线程池,返回的线程池都实现了ExecutorService接口。

1.newFixedThreadPool(int nThreads)

建立了固定线程数目的线程池,每当提交一个任务时就建立一个县城,直到达到线程池的最大数量。当线程池满时,有新的线程要创建,只能放在另外的队列中等待,直到当前的线程中某个线程被移出。

针对一些稳定的并发线程,多用于服务器。

2.newCachedThreadPool()

建立了一个能够缓存的线程池,没有规模限制。调用execute将重用之前构造的线程(若是线程可用)。若是当前没有可用线程,则建立一个新线程;若是当前规模超过了处理需求,将从缓存中终止并移除已经有60秒未使用的线程。

放入该线程池的线程超过timeout不活动,会被自动终止,缺省设置为60秒。

一般用于执行一些生存期很短的异步型任务。

3.newSingleThreadExecutor()

建立了一个单线程的Executor,建立了单个线程来执行任务,若是线程异常结束,会建立另外一个线程来替代。

确保依照任务在队列中的顺序来串行执行(FIFO,LIFO,优先级)。

注意

4.newSechduledThreadPool(int corePoolSize)

与前几种不一样的是,这个方法返回的是ScheduledExecutorService,前边三种返回的是ExecutorService。建立了一个固定长度的线程池,而且是支持定时以及周期性的任务执行的线程池。多数状况下用来替代Timer类。

Timer与ScheduledThreadPoolExecutor

Timer用来负责管理延迟任务以及周期任务,可是它存在一些缺陷,应该使用与ScheduledThreadPoolExecutor来替代它。

Timer类在执行全部定时任务时只会建立一个线程,当有多个定时任务时,就会产生延迟。

当多个定时任务中有一个任务抛出异常,全部的任务都没法执行。

Timer执行周期任务时依赖系统时间。后者不会因为系统时间的改变而发生执行的变化。

提交与执行任务

任务分为两类,一类是实现了Runnable接口的类,一类是实现了Callable接口的类,都能被Executor执行。

Runnable是一种有局限的抽象,由于它不能返回值或者抛出一个受检查的异常。

Callable的call()方法相似于Runnable的run()方法,可是call()方法有返回值,而且可能抛出一个异常。

任务的生命周期包括:建立,提交,开始和完成。在Executor框架中,已经提交但还没有开始的任务能够取消,对于已经开始执行的任务,只有当它们响应中断时,才能取消。

向线程池中提交线程的时候有两种方法:execute()和submit()。

execute()提交只能提交一个Runnable对象,且返回值是void。也就是说提交后若是线程运行,和主线程就脱离了关系。

submit()提交:

  • Future submit(Callable task)
  • Future<?> submit(Runnable task)
  • Future submit(Callable task,T result)

submit()方法能够提交一个Callable接口的对象,也能提交一个Ruuable的对象。使用这种方式提交会返回一个Future对象,表明了该线程的执行结果,主线程经过get方法获取到从线程中返回的结果数据。使用Future能够表示一个任务的生命周期,并提供了相应的方法来判断是否已经完成或取消;还能获取任务的结果;并能取消任务。

Future的get()方法:

  • 任务已经完成,返回结果或者抛出一个异常
  • 任务没有完成,将阻塞直至完成
  • 任务抛出异常,get()方法将该异常封装为ExecutionExcepiton并从新抛出,可使用getCause()来获取被封装的初始异常
  • 任务被取消,get()方法将抛出CancellationException

CompletionService

将executor与blockingqueue融合在一块儿,将Callable任务交由它执行,使用相似队列操做的take和poll等方法得到future。也就是在构造函数中建立了阻塞队列来保存执行完成的结果。

为任务设计时限

使用Future.get(long,timeType)为任务设计时限,时限内有结果则返回,超时则抛出TimeOutException

支持时限的还有invokeAll()方法,它将多个任务提交给ExecutorService并获取结果。其参数为一组任务,返回一组Future,按照任务集合中的顺序将Future添加到返回的集合,实现了两者的关联。当全部任务执行完毕,或超市,或中断,该方法将返回。经过get或isCancelled来判断每一个任务的执行状况。

参考资料

相关文章
相关标签/搜索