java
上篇《Java线程的6种状态详解及建立线程的4种方式》
前言:咱们都知道,线程是稀有资源,系统频繁建立会很大程度上影响服务器的使用效率,若是不加以限制,很容易就会把服务器资源耗尽。因此,咱们能够经过建立线程池来管理这些线程,提高对线程的使用率。面试
简而言之,线程池就是管理线程的一个容器,有任务须要处理时,会相继判断核心线程数是否还有空闲、线程池中的任务队列是否已满、是否超过线程池大小,而后调用或建立线程或者排队,线程执行完任务后并不会当即被销毁,而是仍然在线程池中等待下一个任务,若是超过存活时间尚未新的任务就会被销毁,经过这样复用线程从而下降开销。数组
可能有人就会问了,使用线程池有什么好处吗?那不用说,好处天然是有滴。大概有如下:
一、提高线程池中线程的使用率,减小对象的建立、销毁。
二、线程池的伸缩性对性能有较大的影响,使用线程池能够控制线程数,有效的提高服务器的使用资源,避免因为资源不足而发生宕机等问题。(建立太多线程,将会浪费必定的资源,有些线程未被充分使用;销毁太多线程,将致使以后浪费时间再次建立它们;建立线程太慢,将会致使长时间的等待,性能变差;销毁线程太慢,致使其它线程资源饥饿。)缓存
咱们要使用线程池得先了解它是怎么工做的,流程以下图,废话很少说看图就行。核心就是复用线程,下降开销。安全
一、newCachedThreadPool()(工做队列使用的是 SynchronousQueue)
建立一个线程池,若是线程池中的线程数量过大,它能够有效的回收多余的线程,若是线程数不足,那么它能够建立新的线程。
不足:这种方式虽然能够根据业务场景自动的扩展线程数来处理咱们的业务,可是最多须要多少个线程同时处理倒是咱们没法控制的。
优势:若是当第二个任务开始,第一个任务已经执行结束,那么第二个任务会复用第一个任务建立的线程,并不会从新建立新的线程,提升了线程的复用率。
做用:该方法返回一个能够根据实际状况调整线程池中线程的数量的线程池。即该线程池中的线程数量不肯定,是根据实际状况动态调整的。
二、newFixedThreadPool()(工做队列使用的是 LinkedBlockingQueue)
这种方式能够指定线程池中的线程数。若是满了后又来了新任务,此时只能排队等待。
优势:newFixedThreadPool 的线程数是能够进行控制的,所以咱们能够经过控制最大线程来使咱们的服务器达到最大的使用率,同时又能够保证即便流量忽然增大也不会占用服务器过多的资源。
做用:该方法返回一个固定线程数量的线程池,该线程池中的线程数量始终不变,即不会再建立新的线程,也不会销毁已经建立好的线程,自始自终都是那几个固定的线程在工做,因此该线程池能够控制线程的最大并发数。
三、newScheduledThreadPool()
该线程池支持定时,以及周期性的任务执行,咱们能够延迟任务的执行时间,也能够设置一个周期性的时间让任务重复执行。该线程池中有如下两种延迟的方法。
scheduleAtFixedRate 不一样的地方是任务的执行时间,若是间隔时间大于任务的执行时间,任务不受执行时间的影响。若是间隔时间小于任务的执行时间,那么任务执行结束以后,会立马执行,至此间隔时间就会被打乱。
scheduleWithFixedDelay 的间隔时间不会受任务执行时间长短的影响。
做用:该方法返回一个能够控制线程池内线程定时或周期性执行某任务的线程池。
四、newSingleThreadExecutor()
这是一个单线程池,至始至终都由一个线程来执行。
做用:该方法返回一个只有一个线程的线程池,即每次只能执行一个线程任务,多余的任务会保存到一个任务队列中,等待这一个线程空闲,当这个线程空闲了再按 FIFO 方式顺序执行任务队列中的任务。
五、newSingleThreadScheduledExecutor()
只有一个线程,用来调度任务在指定时间执行。
做用:该方法返回一个能够控制线程池内线程定时或周期性执行某任务的线程池。只不过和上面的区别是该线程池大小为 1,而上面的能够指定线程池的大小。
使用示例:服务器
//建立一个会根据须要建立新线程的线程池 ExecutorService executor= Executors.newCachedThreadPool(); for (int i = 0; i < 20; i++) { executor.submit(new Runnable() { @Override public void run() { System.out.println(i); } }); }
这五种线程池都是直接或者间接获取的 ThreadPoolExecutor 实例 ,只是实例化时传递的参数不同。因此若是 Java 提供的线程池知足不了咱们的需求,咱们能够经过 ThreadPoolExecutor 构造方法建立自定义线程池。多线程
public ThreadPoolExecutor( int corePoolSize,//线程池核心线程大小 int maximumPoolSize,//线程池最大线程数量 long keepAliveTime,//空闲线程存活时间 TimeUnit unit,//空闲线程存活时间单位,一共有七种静态属性(TimeUnit.DAYS天,TimeUnit.HOURS小时,TimeUnit.MINUTES分钟,TimeUnit.SECONDS秒,TimeUnit.MILLISECONDS毫秒,TimeUnit.MICROSECONDS微妙,TimeUnit.NANOSECONDS纳秒) BlockingQueue<Runnable> workQueue,//工做队列 ThreadFactory threadFactory,//线程工厂,主要用来建立线程(默认的工厂方法是:Executors.defaultThreadFactory()对线程进行安全检查并命名) RejectedExecutionHandler handler//拒绝策略(默认是:ThreadPoolExecutor.AbortPolicy不执行并抛出异常) )
使用示例:架构
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 20, 2, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5));
jdk 中提供了四种工做队列:
①ArrayBlockingQueue
基于数组的有界阻塞队列,按 FIFO 排序。新任务进来后,会放到该队列的队尾,有界的数组能够防止资源耗尽问题。当线程池中线程数量达到 corePoolSize 后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。若是队列已是满的,则建立一个新线程,若是线程数量已经达到 maxPoolSize,则会执行拒绝策略。
②LinkedBlockingQuene
基于链表的***阻塞队列(其实最大容量为 Interger.MAX_VALUE),按照 FIFO 排序。因为该队列的近似***性,当线程池中线程数量达到 corePoolSize 后,再有新任务进来,会一直存入该队列,而不会去建立新线程直到 maxPoolSize,所以使用该工做队列时,参数 maxPoolSize 实际上是不起做用的。
③SynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,若是没有可用线程,则建立新线程,若是线程数量达到 maxPoolSize,则执行拒绝策略。
④PriorityBlockingQueue
具备优先级的***阻塞队列,优先级经过参数 Comparator 实现。并发
当工做队列中的任务已到达最大限制,而且线程池中的线程数量也达到最大限制,这时若是有新任务提交进来,就会执行拒绝策略。jdk中提供了4中拒绝策略:
①ThreadPoolExecutor.CallerRunsPolicy
该策略下,在调用者线程中直接执行被拒绝任务的 run 方法,除非线程池已经 shutdown,则直接抛弃任务。
②ThreadPoolExecutor.AbortPolicy
该策略下,直接丢弃任务,并抛出 RejectedExecutionException 异常。
③ThreadPoolExecutor.DiscardPolicy
该策略下,直接丢弃任务,什么都不作。
④ThreadPoolExecutor.DiscardOldestPolicy
该策略下,抛弃进入队列最先的那个任务,而后尝试把此次拒绝的任务放入队列。
除此以外,还能够根据应用场景须要来实现 RejectedExecutionHandler 接口自定义策略。ide
本文简单介绍了线程池的一些相关知识,相信你们对线程池的优势,线程池的生命周期,线程池的工做流程及线程池的使用有了一个大概的了解,也但愿能对有须要的人提供一点帮助!文中有错误的地方,还请留言给予指正,谢谢~
也欢迎你们关注个人公众号:Java的成神之路,免费领取最新面试资料,技术电子书,架构进阶相关资料等。