上篇《Java线程的6种状态详解及创建线程的4种方式》
前言:我们都知道,线程是稀有资源,系统频繁创建会很大程度上影响服务器的使用效率,如果不加以限制,很容易就会把服务器资源耗尽。所以,我们可以通过创建线程池来管理这些线程,提升对线程的使用率。

1、什么是线程池?

简而言之,线程池就是管理线程的一个容器,有任务需要处理时,会相继判断核心线程数是否还有空闲、线程池中的任务队列是否已满、是否超过线程池大小,然后调用或创建线程或者排队,线程执行完任务后并不会立即被销毁,而是仍然在线程池中等待下一个任务,如果超过存活时间还没有新的任务就会被销毁,通过这样复用线程从而降低开销。

2、使用线程池有什么优点?

可能有人就会问了,使用线程池有什么好处吗?那不用说,好处自然是有滴。大概有以下:
1、提升线程池中线程的使用率,减少对象的创建、销毁。
2、线程池的伸缩性对性能有较大的影响,使用线程池可以控制线程数,有效的提升服务器的使用资源,避免由于资源不足而发生宕机等问题。(创建太多线程,将会浪费一定的资源,有些线程未被充分使用;销毁太多线程,将导致之后浪费时间再次创建它们;创建线程太慢,将会导致长时间的等待,性能变差;销毁线程太慢,导致其它线程资源饥饿。)

3、线程池的核心工作流程(重要)

我们要使用线程池得先了解它是怎么工作的,流程如下图,废话不多说看图就行。核心就是复用线程,降低开销。

4、线程池的五种状态生命周期

  • RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务。
  • SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown() 方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用 shutdown() 方法进入该状态)。
  • STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态。
  • TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入 TERMINATED 状态。
  • TERMINATED:在 terminated() 方法执行完后进入该状态,默认 terminated() 方法中什么也没有做。

5、创建线程池的几种方式

  • 通过 Executors 工厂方法创建
  • 通过 new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) 自定义创建
    相对而言,更建议用第二个创建线程池,Executors 创建的线程池内部很多地方用到了无界任务队列,在高并发场景下,无界任务队列会接收过多的任务对象,严重情况下会导致 JVM 崩溃,一些大厂也是禁止使用 Executors 工厂方法去创建线程池。newFixedThreadPool 和 newSingleThreadExecutor 的主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM;newCachedThreadPool 和 newScheduledThreadPool 的主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。

5.1、Executors 五个工厂方法创建不同线程池的区别


1、newCachedThreadPool()(工作队列使用的是 SynchronousQueue)
创建一个线程池,如果线程池中的线程数量过大,它可以有效的回收多余的线程,如果线程数不足,那么它可以创建新的线程。
不足:这种方式虽然可以根据业务场景自动的扩展线程数来处理我们的业务,但是最多需要多少个线程同时处理却是我们无法控制的。
优点:如果当第二个任务开始,第一个任务已经执行结束,那么第二个任务会复用第一个任务创建的线程,并不会重新创建新的线程,提高了线程的复用率。
作用:该方法返回一个可以根据实际情况调整线程池中线程的数量的线程池。即该线程池中的线程数量不确定,是根据实际情况动态调整的。
2、newFixedThreadPool()(工作队列使用的是 LinkedBlockingQueue)
这种方式可以指定线程池中的线程数。如果满了后又来了新任务,此时只能排队等待。
优点:newFixedThreadPool 的线程数是可以进行控制的,因此我们可以通过控制最大线程来使我们的服务器达到最大的使用率,同时又可以保证即使流量突然增大也不会占用服务器过多的资源。
作用:该方法返回一个固定线程数量的线程池,该线程池中的线程数量始终不变,即不会再创建新的线程,也不会销毁已经创建好的线程,自始自终都是那几个固定的线程在工作,所以该线程池可以控制线程的最大并发数。
3、newScheduledThreadPool()
该线程池支持定时,以及周期性的任务执行,我们可以延迟任务的执行时间,也可以设置一个周期性的时间让任务重复执行。该线程池中有以下两种延迟的方法。
scheduleAtFixedRate 不同的地方是任务的执行时间,如果间隔时间大于任务的执行时间,任务不受执行时间的影响。如果间隔时间小于任务的执行时间,那么任务执行结束之后,会立马执行,至此间隔时间就会被打乱。
scheduleWithFixedDelay 的间隔时间不会受任务执行时间长短的影响。
作用:该方法返回一个可以控制线程池内线程定时或周期性执行某任务的线程池。
4、newSingleThreadExecutor()
这是一个单线程池,至始至终都由一个线程来执行。
作用:该方法返回一个只有一个线程的线程池,即每次只能执行一个线程任务,多余的任务会保存到一个任务队列中,等待这一个线程空闲,当这个线程空闲了再按 FIFO 方式顺序执行任务队列中的任务。
5、newSingleThreadScheduledExecutor()
只有一个线程,用来调度任务在指定时间执行。
作用:该方法返回一个可以控制线程池内线程定时或周期性执行某任务的线程池。只不过和上面的区别是该线程池大小为 1,而上面的可以指定线程池的大小。
使用示例:

//创建一个会根据需要创建新线程的线程池
ExecutorService executor= Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {executor.submit(new Runnable() {@Overridepublic void run() {System.out.println(i);}});
}

这五种线程池都是直接或者间接获取的 ThreadPoolExecutor 实例 ,只是实例化时传递的参数不一样。所以如果 Java 提供的线程池满足不了我们的需求,我们可以通过 ThreadPoolExecutor 构造方法创建自定义线程池。

5.2、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));

5.2.1、工作队列

jdk 中提供了四种工作队列:
①ArrayBlockingQueue
基于数组的有界阻塞队列,按 FIFO 排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到 corePoolSize 后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到 maxPoolSize,则会执行拒绝策略。
②LinkedBlockingQuene
基于链表的无界阻塞队列(其实最大容量为 Interger.MAX_VALUE),按照 FIFO 排序。由于该队列的近似无界性,当线程池中线程数量达到 corePoolSize 后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到 maxPoolSize,因此使用该工作队列时,参数 maxPoolSize 其实是不起作用的。
③SynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到 maxPoolSize,则执行拒绝策略。
④PriorityBlockingQueue
具有优先级的无界阻塞队列,优先级通过参数 Comparator 实现。

5.2.2、拒绝策略

当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,就会执行拒绝策略。jdk中提供了4中拒绝策略:
①ThreadPoolExecutor.CallerRunsPolicy
该策略下,在调用者线程中直接执行被拒绝任务的 run 方法,除非线程池已经 shutdown,则直接抛弃任务。
②ThreadPoolExecutor.AbortPolicy
该策略下,直接丢弃任务,并抛出 RejectedExecutionException 异常。
③ThreadPoolExecutor.DiscardPolicy
该策略下,直接丢弃任务,什么都不做。
④ThreadPoolExecutor.DiscardOldestPolicy
该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列。
除此之外,还可以根据应用场景需要来实现 RejectedExecutionHandler 接口自定义策略。

6、线程池的关闭

  • shutdown():
    1、调用之后不允许继续往线程池内添加线程;
    2、线程池的状态变为 SHUTDOWN 状态;
    3、所有在调用 shutdown() 方法之前提交到 ExecutorSrvice 的任务都会执行;
    4、一旦所有线程结束执行当前任务,ExecutorService 才会真正关闭。
  • shutdownNow():
    1、该方法返回尚未执行的 task 的 List;
    2、线程池的状态变为 STOP 状态;
    3、尝试停止所有的正在执行或暂停任务的线程。
    简单点来说,就是:
    shutdown() 调用后,不可以再 submit 新的 task,已经 submit 的将继续执行
    shutdownNow() 调用后,试图停止当前正在执行的 task,并返回尚未执行的 task 的 list

7、总结

本文简单介绍了线程池的一些相关知识,相信大家对线程池的优点,线程池的生命周期,线程池的工作流程及线程池的使用有了一个大概的了解,也希望能对有需要的人提供一点帮助!文中有错误的地方,还请留言给予指正,谢谢~
也欢迎大家关注我的公众号:Java的成神之路,免费领取最新面试资料,技术电子书,架构进阶相关资料等。

线程池的五种状态及创建线程池的几种方式相关推荐

  1. ThreadPoolExecutor的参数与线程池的五个状态

    ThreadPoolExecutor,它是Executors.newXxxxx()的返回结果,像Executors.newCachedThreadPool();,它实际上是这个: public sta ...

  2. java线程6种状态转换,Java线程的生命周期和各种状态转换详解

    在Java中,任何对象都有生命周期,线程也不例外,它也有自己的生命周期.当Thread对象创建完成时,线程的生命周期便开始了,当线程任务中代码正常执行完毕或者线程抛出一个未捕获的异常(Exceptio ...

  3. java 创建线程的三种方法_java 创建线程的几种方式

    说道线程,肯定会想到使用 java.lang.Thread.java这个类 那么创建线程也主要有2种方式 第一种方式: public class MyThread extends Thread { p ...

  4. java创建线程几种_java中创建线程有几种方式

    详细内容 线程的创建方式 1.继承Thread类实现多线程 2.覆写Runnable()接口实现多线程,而后同样覆写run().推荐此方式 3.使用Callable和Future创建线程 相关视频教程 ...

  5. Day25(线程同步安全问题,SellticketLock,DieLock,ThredGroup,ThreadPool,Timer,线程安全的类,匿名内部类的形式创建线程对象)

    一.线程同步安全问题1 package com.shujia.lhw.day25.demo1; /*     分析:       共享数据:同一个学生对象Student       生产者:SetTh ...

  6. 对象头、锁的四种状态、Java和处理器实现原子操作的方式(CAS、锁机制;总线锁定、缓存锁定)

    1.对象头 Java对象头里的Mark Word里默认存储对象的HashCode.分代年龄和锁标记位. 32位JVM的Mark Word的默认存储结构如下图所示: 在运行期间,Mark Word里存储 ...

  7. 主线程是如何向子线程传递数据的?_c++ 利用thread创建线程

    用进行多线程开发 小时候,老师总是教育我们上课要专心,"一心不可二用".可是CPU这个不听话的"熊孩子"偏偏却在一个芯片中加入了两个甚至多个运算核心,想要一&q ...

  8. java 线程中创建线程_如何在Java 8中创建线程安全的ConcurrentHashSet?

    java 线程中创建线程 在JDK 8之前,还没有办法在Java中创建大型的线程安全的ConcurrentHashSet. java.util.concurrent包甚至没有一个名为Concurren ...

  9. 多线程基础:两种常见的创建线程的方式

    一 通过继承Thread 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package thread; publi ...

最新文章

  1. cufflinks基于dataframe数据绘制股票数据:散点图(scatter plot)、价差图
  2. 解决set /p yn= 接受键盘输入导致ECHO 处于关闭状态的问题
  3. Go语言的管道Channel用法
  4. 科技日报头版显要位置报道国内多家企业投融资给力永中软件
  5. 图像重建算法_降噪重建技术路在何方?
  6. kubernetes英语怎么读_陷阱英语单词怎么读?
  7. 从DevOps到Cloud Native,应用上云姿势全解锁
  8. 牛客小白月赛4 J 强迫症 思维
  9. 随书光盘资源下载/提取码(二)
  10. 天龙八部科举答题问题和答案(全3/8)
  11. webpack之css/js/html文件的压缩
  12. 第7讲 替代定理、戴维南定理、诺顿定理
  13. DE2带的IP核ISP12362报错问题解决 Error:avalon_slave_1_irq: associatedAddressablePoint out of range...
  14. AMOLED真的比LCD屏幕更伤眼吗
  15. rtge更好发挥士大夫广告的通过合同
  16. oracle rba一些小知识
  17. 从码云上克隆代码,修改完之后,在提交上去(图解)
  18. bootstrap开发的新闻网站
  19. android zip解压 速度,如何在Java / Android中加快解压缩时间?
  20. 一张图带走一套操作 分享最新网络营销学习路线图-千锋

热门文章

  1. 造梦西游5幻宇辅助_全网最好用、最强大的辅助
  2. 移动应用交互设计_了解移动应用程序设计中的微交互
  3. b站前端大佬_在B站如何养成6级大佬?大四学生发明养号神器,看完你也会
  4. 易学笔记-python计算个人所得税
  5. 联想E480高配置却很卡如何提升?
  6. 驼峰设计 PPT美化
  7. yolov3的训练(四)VOC数据集的错误
  8. 魅蓝和vivo怎么打开开发者选项或者打开usb调试(设置~关于本机~连续点击7次版本)
  9. 10万元人民币,存银行3年,利息1万块钱,算高吗?
  10. UPA均匀面阵,MIMO,LoS信道信号传播延迟时间推导