阿里开发手册有一段描述:
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
参考文献:
https://www.javazhiyin.com/71844.html
https://blog.csdn.net/qq_41135605/article/details/114453105
继承extend类或者实现runnable接口都是最简单创建线程的方式,但是这种方式有以下缺点:
不受风险控制
频繁创建,开销大
不好管理(不知道哪里创建了线程、线程名字可能没有)
服务器CPU资源有限,如果每个人都显示手动创建线程,不知道哪里的代码出现了多线程,在运行的时候所有线程都在抢占资源,不好控制。
虽然说new Theard()的父类也是Object,但是new Theard()和new Object()还是有区别的。
分配一块内存 M
在内存 M 上初始化该对象
将内存 M 的地址赋值给引用变量 obj
它为一个线程栈分配内存,该栈为每个线程方法调用保存一个栈帧
每一栈帧由一个局部变量数组、返回值、操作数堆栈和常量池组成
一些支持本机方法的 jvm 也会分配一个本机堆栈
每个线程获得一个程序计数器,告诉它当前处理器执行的指令是什么
系统创建一个与Java线程对应的本机线程
将与线程相关的描述符添加到JVM内部数据结构中
线程共享堆和方法区域
这段描述稍稍有点抽象,用数据来说明创建一个线程(即便不干什么)需要多大空间呢?答案是大约 1M 左右
线程的名字不统一,可能无限制新建线程,可能占用过多系统资源导致死机或OOM。而重用存在的线程,减少对象创建、消亡的开销,性能佳。
在Java 5之后,Java通过Executor来启动线程,比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免this逃逸问题。
简单的说就是把线程统一管理。
线程池优势:
(1)降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
(4)提供更强大的功能,延时定时线程池。
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。