为什么需要线程池

我们有两种常见的创建线程的方法,一种是继承Thread类,一种是实现Runnable的接口,Thread类其实也是实现了Runnable接口。但是我们创建这两种线程在运行结束后都会被虚拟机销毁,如果线程数量多的话,频繁的创建和销毁线程会大大浪费时间和效率,更重要的是浪费内存。那么有没有一种方法能让线程运行完后不立即销毁,而是让线程重复使用,继续执行其他的任务哪?

这就是线程池的由来,很好的解决线程的重复利用,避免重复开销。

线程池的优点

1、线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用。

2、可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。

线程池的风险

虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险,诸如同步错误和死锁,它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足和线程泄漏。

1.死锁

任何多线程应用程序都有死锁风险。当一组进程或线程中的每一个都在等待一个只有该组中另一个进程才能引起的事件时,我们就说这组进程或线程 死锁了。死锁的最简单情形是:线程 A 持有对象 X 的独占锁,并且在等待对象 Y 的锁,而线程 B 持有对象 Y 的独占锁,却在等待对象 X 的锁。除非有某种方法来打破对锁的等待(Java 锁定不支持这种方法),否则死锁的线程将永远等下去。

2.资源不足

线程池的一个优点在于:相对于其它替代调度机制(有些我们已经讨论过)而言,它们通常执行得很好。但只有恰当地调整了线程池大小时才是这样的。

线程消耗包括内存和其它系统资源在内的大量资源。除了
Thread 对象所需的内存之外,每个线程都需要两个可能很大的执行调用堆栈。除此以外,JVM 可能会为每个 Java
线程创建一个本机线程,这些本机线程将消耗额外的系统资源。最后,虽然线程之间切换的调度开销很小,但如果有很多线程,环境切换也可能严重地影响程序的性能。

如果线程池太大,那么被那些线程消耗的资源可能严重地影响系统性能。在线程之间进行切换将会浪费时间,而且使用超出比您实际需要的线程可能会引起资源匮乏问题,因为池线程正在消耗一些资源,而这些资源可能会被其它任务更有效地利用。

除了线程自身所使用的资源以外,服务请求时所做的工作可能需要其它资源,例如 JDBC 连接、套接字或文件,这些也都是有限资源,有太多的并发请求也可能引起失效,例如不能分配 JDBC 连接。

3.并发错误

线程池和其它排队机制依靠使用
wait() 和 notify()
方法,这两个方法都难于使用。如果编码不正确,那么可能丢失通知,导致线程保持空闲状态,尽管队列中有工作要处理。使用这些方法时,必须格外小心;即便是专家也可能在它们上面出错。而最好使用现有的、已经知道能工作的实现,例如在
util.concurrent 包。

4.线程泄漏

各种类型的线程池中一个严重的风险是线程泄漏,当从池中除去一个线程以执行一项任务,而在任务完成后该线程却没有返回池时,会发生这种情况。发生线程泄漏的一种情形出现在任务抛出一个 RuntimeException 或一个 Error 时。

如果池类没有捕捉到它们,那么线程只会退出而线程池的大小将会永久减少一个。当这种情况发生的次数足够多时,线程池最终就为空,而且系统将停止,因为没有可用的线程来处理任务。

5.请求过载

仅仅是请求就压垮了服务器,这种情况是可能的。在这种情形下,我们可能不想将每个到来的请求都排队到我们的工作队列,因为排在队列中等待执行的任务可能会消耗太多的系统资源并引起资源缺乏。在这种情形下决定如何做取决于您自己;在某些情况下,您可以简单地抛弃请求,依靠更高级别的协议稍后重试请求,您也可以用一个指出服务器暂时很忙的响应来拒绝请求。

线程池的实现原理

线程池

1.线程池状态

线程池和线程一样拥有自己的状态,在ThreadPoolExecutor类中定义了一个volatile变量runState来表示线程池的状态,线程池有四种状态,分别为RUNNING、SHURDOWN、STOP、TERMINATED。

  • 线程池创建后处于RUNNING状态。
  • 调用shutdown后处于SHUTDOWN状态,线程池不能接受新的任务,会等待缓冲队列的任务完成。
  • 调用shutdownNow后处于STOP状态,线程池不能接受新的任务,并尝试终止正在执行的任务。
  • 当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

线程池原理:预先启动一些线程,线程无限循环从任务队列中获取一个任务进行执行,直到线程池被关闭。如果某个线程因为执行某个任务发生异常而终止,那么重新创建一个新的线程而已,如此反复。

2.线程池的处理流程

1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。

2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

配置线程池大小配置

一般需要根据任务的类型来配置线程池大小:

如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1

如果是IO密集型任务,参考值可以设置为2*NCPU

当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。

Java提供的四种线程池实现

(1)newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

(2)newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

(3)newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

(4)newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

以上就是线程池的详细介绍,后续将详细讲解四种线程池的具体实现。

你可能也喜欢:

  1. Java多线程系列(二):线程的五大状态,以及线程之间的通信与协作
  2. Java多线程系列(七):并发容器的原理,7大并发容器详解、及使用场景
  3. Java多线程系列(一):最全面的Java多线程学习概述
  4. Java多线程系列(六):深入详解Synchronized同步锁的底层实现
  5. Java多线程系列(十):源码剖析AQS的实现原理
  6. Java多线程系列(四):4种常用Java线程锁的特点,性能比较、使用场景

Java多线程系列(五):线程池的实现原理、优点与风险、以及四种线程池实现相关推荐

  1. Java多线程系列(七):并发容器的原理,7大并发容器详解、及使用场景

    之前谈过高并发编程系列: 高并发编程系列:4种常用Java线程锁的特点,性能比较.使用场景 高并发编程系列:CountDownLatch.Semaphore等4大并发工具类详解 高并发编程系列:4大J ...

  2. Java多线程系列(八):ConcurrentHashMap的实现原理(JDK1.7和JDK1.8)

    HashMap.CurrentHashMap 的实现原理基本都是BAT面试必考内容,阿里P8架构师谈:深入探讨HashMap的底层结构.原理.扩容机制深入谈过hashmap的实现原理以及在JDK 1. ...

  3. Java多线程系列(十一):ReentrantReadWriteLock的实现原理与锁获取详解

    我们继续Java多线程与并发系列之旅,之前我们分享了Synchronized 和 ReentrantLock 都是独占锁,即在同一时刻只有一个线程获取到锁. 然而在有些业务场景中,我们大多在读取数据, ...

  4. Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例

    CyclicBarrier简介 CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point).因为该 barrier 在释放等 ...

  5. java线程安全定义了什么单例_Java中四种线程安全的单例模式实现方式

    第一种:饿汉模式(线程安全) public class Single2 { private static Single2 instance = new Single2(); private Singl ...

  6. Java多线程系列(三):Java线程池的使用方式,及核心运行原理

    之前谈过多线程相关的4种常用Java线程锁的特点,性能比较.使用场景,今天主要分享线程池相关的内容,这些都是属于Java面试的必考点. 为什么需要线程池 java中为了提高并发度,可以使用多线程共同执 ...

  7. Java多线程系列(二):线程的五大状态,以及线程之间的通信与协作

    在Java面试的时候,经常会问到Java并发编程相关的多线程.线程池.线程锁.线程通信等面试必考点,比如: Java并发编程系列:Java线程池的使用方式,核心运行原理.以及注意事项 Java并发编程 ...

  8. Java多线程系列(十):源码剖析AQS的实现原理

    在并发编程领域,AQS号称是并发同步组件的基石,很多并发同步组件都是基于AQS实现,所以想掌握好高并发编程,你需要掌握好AQS. 本篇主要通过对AQS的实现原理.数据模型.资源共享方式.获取锁的过程, ...

  9. Java多线程系列(六):深入详解Synchronized同步锁的底层实现

    谈到多线程就不得不谈到Synchronized,很多同学只会使用,缺不是很明白整个Synchronized的底层实现原理,这也是面试经常被问到的环节,比如: synchronized的底层实现原理 s ...

最新文章

  1. 制作精美的网站首页模板应该如何操作?
  2. 使用Nginx+FFMPEG搭建HLS直播转码服务器
  3. 野生前端的数据结构基础练习(5)——散列
  4. oracle中命令,oracle中常用命令汇总(一)
  5. 深度学习技术在社会化推荐场景中的总结(附数据集)
  6. PyTorch 深度学习:33分钟快速入门——VGG
  7. JDK 中Future模式使用
  8. Bailian2726 采药【模拟】
  9. 开源 免费 java CMS - FreeCMS1.5-数据对象-info
  10. 禁忌搜索算法原理步骤、matlab实现算例及相关代码
  11. 算法设计之数字三角形问题
  12. 联想拯救者 Legion Y7000P 安装 Ubuntu 18.04.2 LTS amd64 遇到的问题解决
  13. 如何写一个简单的时钟表盘(qt)
  14. 2021Q2产品团队绩效总结--管理日记
  15. PEG 动态选股策略
  16. DNS服务详解(解析+搭建)
  17. 如何在EDUIS中导出ETL字幕模板_Arctime教程——将字幕导出到剪辑/合成软件
  18. 电巢:上海半导体投资浪潮的前奏、高潮与转折
  19. WPF框架嵌套用户控件,显示与切换(详细,代码复制可用)
  20. 12.10 Daily Scrum

热门文章

  1. android第一天-------环境搭建
  2. linux下ftp服务器的搭建
  3. 在X32与X64下,每种数据类型占用的字节数
  4. C++ functor 仿函数
  5. hdfs为什么要用block以及block大小的设置
  6. SensorKernel层框架分析
  7. android 7.1 apk的systemuid相同导致问题[2]
  8. Android 7.1 bootchart触发后导致不断重启
  9. 每日一题(52)—— 进程
  10. java中自定义异常的_java中的自定义异常(标准)