多线程--线程池
一.线程池的概念:
线程池是一种多线程处理形式,它预先创建并管理一组线程,用于执行提交的任务。线程池的核心思想是复用线程,避免频繁创建和销毁线程带来的性能开销。线程池通常由任务队列和线程集合组成,任务被提交到队列中,由空闲线程从队列中取出并执行。
二.线程池创建时机:
线程池里的核心线程并不是在初始化线程池的时候创建的而是采用懒汉模式的策略,在以下的情况会创建:
当提交第一个任务时:即使核心线程数为N,线程池通常只创建处理第一个任务所需的1个线程
当新任务到来且当前活跃线程数小于核心线程数时:线程池会创建新线程来处理任务,直到达到核心线程数
通过prestartCoreThread()或prestartAllCoreThreads()方法:可以显式地提前启动核心线程
三.为何从线程池中拿线程比创建线程的开销小呢?
这就要从操作系统看起,操作系统是分为用户态和内核态的,内核包括操作系统的各种核心功能,比如管理硬件设备和给软件提供稳定的运行环境,一个操作系统的内核就一份内核,一份内核要给所有的应用程序提供服务支持,而从线程池中取线程纯用户态的应用程序代码就能完成(可控),然而从操作系统中创建线程,就需要操作系统的内核来配合完成(不可控),所以使用线程池就可以省下程序切换到内核中这样的开销
四.java中提供的直接使用线程池
ThreadPoolExecutor类(线程池)
核心submit(Runnable) 通过一个Runnable描述一段要执行的任务(可以单独创建一个类或者匿名内部类或者lambda表达式) 通过submit提交到线程池的任务队列中,线程池中的线程就会从任务队列中读取任务来执行
重要的是其构造方法
一共四种构造方法,最主要的还是第四个构造方法参数最全的,接下来讨论第四种构造方法的各个参数的含义
第一个参数通过名字也能看出是核心线程数,创建时机可以看上述(二), 核心线程创建之后,直至线程池销毁这些线程才会销毁
第二个参数是线程池中最大线程的数量,线程池中的线程数量包括核心线程数和非核心线程数(自适应) 非核心线程在任务繁忙的时候就创建,不繁忙的时候就销毁,线程并不是越多越好,线程过多有些线程根本没机会拿到任务,操作系统还会因线程的调度,性能下降
第三个参数是非核心线程允许存在的时间,这个就是非核心线程创建后过了这个参数的时间之后就会自动销毁,以免任务不繁忙的时候非核心线程占用cup,内存等资源
第四个参数是时间的枚举类也就是第三个参数非核心线程存在时间的时间单位,一般都是秒SECONDS或者分钟MINUTES
第五个参数是线程池中的任务队列, 需要自己传入,可以使用数组/链表,指定capacity,指定优先级,是否带有阻塞功能等等..
线程池本身也是生产者消费者模型 调用submit方法就是生产任务, 线程池里的线程就是在消费任务, 需要一个任务队列
第六个参数是线程的工厂类(工厂模式可以参照前面文章), 给线程类提供的工厂类,尤其统一构造并线程且初始化线程的属性
第七个参数是线程池的拒绝策略,任务队列是阻塞队列,等任务队列满的时候再添加任务就会产生阻塞,当然我们不希望阻塞太多或阻塞太长时间,在业务逻辑中线程调用submit就阻塞无法响应用户的请求,用户等很久都拿不到响应,就是直观上的卡了,与其是卡了不如直接告诉请求失败
所以在线程池中发现队列满了,任务入队列的时候,不会真正的入队列,也不会真阻塞而是执行拒绝策略的代码
上述的线程池的创建太过于麻烦,java中封装好了更简单的接口ExcutorService和实现其方法的类Excutors(包含各种实现方法)↓
ExecutorService threadPool = Executors.newCachedThreadPool();//最大线程是一个很大的数字(可以无限增加)
newCachedThreadPool创建出来的线程池参数可以指定核心线程数这里的核心线程数和非核心线程数一样如果不指定就是一个很大的数, 其他的参数默认情况如下
核心线程数 (
corePoolSize
):
- 默认值为
0
。这意味着如果没有任务需要执行,线程池中不会保留任何线程。最大线程数 (
maximumPoolSize
):
- 默认值为
Integer.MAX_VALUE
。这意味着线程池可以创建的最大线程数是 Java 虚拟机允许的最大值,这个数字非常大,通常可以认为是无限增加的。线程空闲时间 (
keepAliveTime
):
- 默认值为
60
秒。这意味着当线程空闲超过 60 秒时,线程会被终止并从线程池中移除。任务队列 (
workQueue
):
- 默认使用
SynchronousQueue
。这是一个无界阻塞队列,它不存储元素,而是直接将任务提交给线程执行。如果当前没有空闲线程可用,新的任务会导致创建新的线程。拒绝策略 (
RejectedExecutionHandler
):
- 默认使用
AbortPolicy
。这意味着当线程池无法接受新任务时(例如,所有线程都在忙且队列已满),会抛出RejectedExecutionException
异常。
下面是自己实现一个固定线程个数的线程池的简单代码
//实现一个固定线程个数的线程池
class MyThreadPool {private BlockingQueue<Runnable> queue = null;Thread[] Threads = null;public MyThreadPool(int n) {//这里使用ArrayBlockingQueue(阻塞队列)作为线程池的队列,容量为1000queue = new ArrayBlockingQueue<Runnable>(1000);Threads = new Thread[n];//初始化线程池,创建n个线程for (int i = 0; i < n; i++) {Threads[i] = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {try {Runnable task = queue.take();task.run();} catch (InterruptedException e) {Thread.currentThread().interrupt();break;}}});Threads[i].start();}}//提交任务public void submit(Runnable task) throws InterruptedException {//将任务放入队列中queue.put(task);}//关闭线程池public void shutdown() {while (!queue.isEmpty()) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}for (Thread thread : Threads) {thread.interrupt();}}
}