Java:看一波线程池,反正也不亏

前言

线程池在Java并发编程中,有着举足轻重的位置,学习和掌握它是学习Java的重中之重。反正有空看看,学点知识,又不亏。编程

在开发中,合理使用线程池能带来什么好处呢?数组

  • 提升响应速度。当任务到达时,线程已经创建好,当即执行。
  • 下降资源消耗。经过重复使用线程下降新建和销毁线程带来的开销。
  • 提升线程的可管理性。线程池能够进行统一分配、调优和监控线程的状况,对资源的管控。

队列

线程池内部持有一个用于存储工做任务的队列,在核心线程满了时候,会将任务存储到队列中。经常使用队列类型:bash

  • LinkedBlockingQueue:是基于链表结构的有界阻塞队列 。
  • ArraryBlockingQueue:是基于数组结构的有界阻塞队列。
  • SynchronousQueue:一个不存储元素的阻塞队列。每一个插入操做必须等到一个移除操做,下一个插入操做才能进行。
  • PriorityBlocking:具备优先级的无界阻塞队列。
  • DelayQueue:延时的无界阻塞队列。

线程池的使用

  1. 建立
ThreadPoolExecutor( int corePoolSize,
                    int maximumPoolSize,
                    long keepAliveTime,
                    TimeUnit unit, 
                    BlockingQueue<Runnable> workQueue, 
                    ThreadFactory threadFactory,
                    RejectedExecutionHandler handler)
  
复制代码

在线程的构造方法中,共有5个参数:并发

  • corePoolSize:线程池核心线程的数量。当线程池的线程数量未达到核心线程数量,每提交一个任务都会建立一个线程,直到线程数量等于核心线程数量。核心线程指一直存活在线程池中,不会被会销毁(可设置超时销毁),直到线程池关闭。而超过corePoolSize数量建立的线程的就是非核心线程,在空闲keepAliveTime时间后被销毁。
  • maximumPoolSize:线程池最大线程数量。核线程最大数量=核心线程数量+非核心检查数量。使用无限的任务队列(如PriorityBlocking),会致使该参数失效。
  • keepAliveTime:非核心线程闲置时长。闲置超过该时长,非核心线程会被回收。
  • unit:keepAliveTime的时间单位。
  • workQueue:工做队列,如前文讲到的四种队列。
  • threadFactory:用于建立线程的工厂。能够经过Executors.defaultThreadFactory();得到默认的线程工厂。主要做用就是给线程起个名字而已。
  • handler:饱和策略。当队列和线程池都满了,此时该怎么处理新任务?ThreadPoolExecutor内有四个内部类实现的策略供选择。AbortPolicy:直接抛出异常;DiscardPolicy:不处理,抛弃新任务;DiscardOldestPolicy:抛弃队列总最近的一个队列;CallerRunsPolicy:使用调用者所在线程执行新任务。默认AbortPolicy策略。
  1. 提交任务到线程池
//execute()方法执行任务
executor.execute(new Runnable() {
	@Override
	public void run() {
		// TODO Auto-generated method stub
	}
});
//submit()方法提交任务
executor.execute(new Runnable() {
	@Override
	public void run() {
	// TODO Auto-generated method stub
	}
});
复制代码

提交到线程池中有种方式:execute()方法和submit()方法。ide

execute()方法提交的任务没法得到返回值,没法判断提交状态。 submit()方法能够得到返回Future类型的对象,根据Future对象能够判断任务是否执行成功和经过get()方法得到返回值,但get()方法会阻塞当前线程一段时间。学习

  1. 线程池的关闭
  • shutdown() 将线程池的状态设置为SHUTDOWN状态。而后中断全部没有正在执行任务的线程。
  • shutdownNow() 将线程池的状态设置为STOP状态。尝试中止全部正在执行或者暂停任务的线程,并返回等待执行任务的列表。

因为二者都是遍历线程池中的工做线程,而后中断线程,因此没法响应中断的线程可能永远没法终止。ui

线程池的实现原理

当咱们提交一个新任务到线程池时,任务在线程池的流程是怎样的呢?spa

  1. 核心线程是否已满。核心线程大于等于corePoolSize,表示已满。不是的话,则会建立新的核心线程执行新任务(只要核心线程未达到数量corePoolSize,都会新建)。否则执行下一步骤。
  2. 工做队列是否已经满。在有界队列中,存储任务的数量有限。若是任务未满了,新提交任务存储到工做队列。不然进入下个流程。
  3. 非核心线程是否已满。虽然核心线程队列已满,但非核心线程不必定满了。非核心线程大于maximumPoolSize,表示已满,线程池拒绝新任务,交给饱和策略处理。不然新建非核心线程处理新任务。

任务队列的任务何时被处理?线程

线程池中线程处理任务有两种方式:一种就是新建线程处理任务,另外一种就是循环从阻塞队列获取任务来执行。code

四种线程池

有时咱们仅仅是使用一下线程池,不会本身定制线程池,毕竟线程池的构造方法参数那么多,个人妈耶。那看看类Executors提供的四个线程池。

FixedThreadPool

//建立FixedThreadPool线程池
    Executors.newFixedThreadPool(nThreads); 
    
    //newFixedThreadPool方法的实现
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

复制代码

建立一个FixedThreadPool线程池,线程的核心线程coreThreadSize和线程池线程容量maximumPoolSize都为nThreads。因为使用的无界的LinkedBlockingQueue队列,将致使maximumPoolSize参数失效,队列对任务未来者不拒。这里将保活时间设为0,意味着空线程会被当即终止。

CachedThreadPool

//建立CachedThreadPool线程池
    Executors.newCachedThreadPool();
    
    //newCachedThreadPool方法的实现
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
复制代码

CachedThreadPool的核心线程为0,线程池容量为Integer.MAX_VALUE,意味着maximumPoolSize是无界的。保活时间keepAliveTime设为60秒,空闲线程闲置60秒后被终止。因为使用了没有容量的SynchronousQueue队列,意味当提交一个新任务到线程池中,没有空闲线程来对接,就会新建新的线程来处理新任务。

SingleThreadExecutor

//建立SingleThreadExecutor线程池
    Executors.newSingleThreadExecutor();
    
    //SingleThreadExecutor方法的实现
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
复制代码

能够看到SingleThreadExecutor能够看作定制化的FixedThreadPool,将nThreads置为1,将核心线程和线程池容量设为1,以保证只有一个线程在执行。

ScheduledThreadPool

//建立ScheduledThreadPool线程池
    Executors.newScheduledThreadPool(corePoolSize);
    
    //newScheduledThreadPool方法的实现
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
    //ScheduledThreadPoolExecutor方法的实现
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
复制代码

newScheduledThreadPool方法最终会调用ThreadPoolExecutor的构造来建立线程池。将定制化DelayQueue后的DelayedWorkQueue做为工做队列。DelayedWorkQueue队列会把执行时间小的任务排在前面优先执行。若是执行时间相同,就会优先执行提交时间早的任务。

FutureTask

在经过submit()方法提交任务到线程池,会返回有结果的Future类型的对象。Future是一个接口,FutureTask继承它,因此FutureTask也能够做为submit()方法得返回值。同时,FutureTask继承Runnable接口,这样又能够做为任务提交到线程池中,由调用线程直接执行。

当调用FutureTask的get()方法,若是FutureTask处于已完成状态(执行完毕),则会致使调用线程当即放回或者抛出异常。不然会使调用线程阻塞。

当调用FutureTask的cancel()方法,若是FutureTask处于未启动状态(为执行run方法),则该任务不会被执行;若是处于启动状态,会尝试以中断来尝试中止任务。若是已经完成状态,则返回false。

总结

经过对线程池知识点理解,能够清晰掌握建立线程池骚姿式和内涵。觉得实战提供必要的理论知识。

点个赞,老铁

若是以为文章有用,给文章点个赞,铁子

本文是我的学习总结和知识备忘,如知识有误或片面,请多加指正,谢谢

知识来源《Java并发编程的艺术》& 互联网 & 《Java并发编程实战》