Java线程池详解

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

✨✨hello愿意点进来的小伙伴们你们好呐
🐻🐻系列专栏【JavaEE】
🐲🐲本篇内容面试常考内容—线程池
🐯🐯作者简介:一名现大二的三非编程小白日复一日仍需努力。

1. 线程池存在的意义

在Java中处理并发场景最开始的使用的是进程,然后因为进程的创建与销毁的开销太大了,进程太重了,所以操作系统就引入了线程,线程被称为轻量级进程,因为线程的创建是基于进程的硬件资源的,所以线程的创建与销毁的开销相对于进程来说就小很多,创建线程,销毁线程,调度线程的开销都对于进程来说小很多

但是随着社会的发展,随着我们对性能要求的提高,在并发量很大的场景下,似乎线程的创建与销毁开销也不是很小,也没有那么的轻量化,所以为了提高效率,大佬们就发明了两种方法 .


第一种方法

大佬们开发了一个"轻量级的线程" – 协程,这个协程创建与销毁的开销比线程更小了,但是遗憾的是这个协程暂时还没有引进Java的标准库内.这里我就 不做过多的介绍


第二种方法

使用线程池来减低创建和销毁线程的开销,线程池就是在还没有执行任务的时候先将线程创建起来,存到一个池子中(某种数据结构),然后通过如果有需要的使用线程执行的任务,就从该线程池中取出任务来执行,然后执行完毕后又将该线程放入线程池中,这样就少了创建与销毁线程的开销了

使用线程池的三个好处 :
    第一 :
降低资源的消耗,通过重复利用已经创建好的线程降低线程创建与销毁造成的消耗;

    第二
提高响应速度,当需要执行的任务到达时,不需要去创建线程来执行,直接使用线程池中的线程执行任务,减少了重新创建线程的时间开销;

    第三
可以提高资源的可管理性,因为对于线程来说,虽然可以创建很多,但是如果无限制的创建也会销毁很多系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配,调优与监控.

2. 线程池简单使用 :

2.1 创建线程池

线程池的创建是使用 Executors 类中的静态方法来创建线程池的,这个是很明显的工厂模式
在这里插入图片描述
但是真正的线程池是ThreadPoolExecutor 类,Executors中的静态方法其实就将ThreadPoolExecutor给封装起来,来使用.
我们可以看到创建线程池的时候会有很多种类的线程池,那么接下来我们来看看这些线程池的不同之处吧


在这里插入图片描述


然后我们会看到在创建线程池的时候会指定很多参数,接下来我们来分析一下这些参数.

2.2 创建线程的参数 :

真正创建线程池的时候,主要是有这几种构造方法,我们来对红框中的构造方法分析,因为他的参数是最多的.
在这里插入图片描述

1. corePollSize && maxmumPollSize

这两个参数分别代表了核心线程数与总线程数,在线程池中将线程分为两类 :
一类是 : 正式的员工 (核心线程数)
一类是 : 实习生(总线程数—核心线程数)

正式的员工 + 实习生就是总员工的数量(总线程数),在公司期间正式员工是允许摸鱼的,实习生是不允许摸鱼的,如果实习生摸鱼太久了,可能就会有开除的风险(销毁).
然后对于一个程序来说,任务一般是有一段时间多,一段时间少的,那么对于任务少的时候如果与任务多的时候线程池中的线程数量要是一样多那就非常不合适了.
所以对于线程池中线程的数量整体的策略就是,正式员工保底,临时工来动态调节

2. keepAliveTime && unit

"keepAliveTime " 这个参数描述了工作线程可以摸鱼的最大时间,就如果临时工在指定的时间内没有执行任务,那么该临时工就会被开除(销毁),等有任务了再创建.

“unit” 这个即是指定了临时工摸鱼时间的时间单位

3. workQueue

线程池的任务队列,使用阻塞队列.

4. threadFactory

用于设置创建线程的工厂,可以通过线程工厂来给每个创建出来的线程赋予更有意义的名称,

5. handler

饱和策略,当线程池中的所有线程都处于工作状态,然后任务队列也满了的情况下,线程池就hi将该任务交给线程池的饱和策略去处理.

而线程池的饱和策略又分为一下几种 :

在这里插入图片描述


关于线程池创建的参数也已经介绍得差不多了,接下来我就来创建一个简单的线程池,并且来执行任务

2.3 简单线程池应用 :

我们创建了一个线程10个线程的线程池,并使用线程池来打印100个hello.
其中使用的 submit(Runnable runnable) 方法就是来往任务队列中插入任务,然后等待线程池中的线程来执行的

public class Demo08 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 100; i++) {
            int n = i;
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(n + "hello");
                }
            });
        }

    }
}

在这里插入图片描述


执行代码后,我们会发现将打印任务执行完毕后,代码其实还没有执行完毕,这个是因为线程池中的线程创建的默认为前台进程,不会随着主线程的销毁而销毁,执行完任务后,只会等到默认的工作线程等待时间后自动销毁,

在这里插入图片描述


了解线程池的创建的细节后,我们应该也对线程池的工作原理感兴趣吧,对线程池是怎么处理任务的流程感兴趣吧

3. 线程池的工作原理 :

3.1 线程池执行流程 :

在线程池中,当要提交一个新的任务交给线程池去处理时,线程池的处理流程如下 :

  1. 线程池会先判断核心线程池中的线程是否都处于工作状态. 若不是,则创建一个新的工作线程来执行新加入的任务. 若是,则进入下一个流程.
  2. 线程池判断任务队列是否满了,如果任务队列没有满,则将该任务加入任务队列,如果任务队列满了,那么就加入下一个流程.
  3. 线程池判断线程池的工作线程数是否小于线程池的最大线程数,如果小于,那么就创建一个新的工作线程来执行该任务. 如果大于,那么就交给饱和策略来处理这个任务

图解:

在这里插入图片描述


我们来通过下面的代码来验证一下 :

创建10个线程的线程池,然后提交5个任务给线程池去执行,那么按照上述的流程的话,那么执行完代码后线程池只会创建五个线程.

public class Demo08 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 5; i++) {
            int n = i;
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(n + "hello");
                }
            });
        }
    }
}


正如上诉,当前线程池的线程数为5个, 这里也可以看成线程池中创建的线程都为前台线程,会影响进程的销毁

在这里插入图片描述


那我们会有疑惑,如果创建线程池然后不插入任务,是否线程池中的线程数就为0呢?

public class Demo08 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(10);
    }
}

我们猜测的没有错误,如果没有任务加入的话,当前线程池中的线程数就为0,那么该因为主线程执行完创建线程池后就销毁了然后带着Java进程也销毁了,所以我们在JConsole中看不到有该进程存在

在这里插入图片描述


在某些创建我们创建线程池后,但是不会立即有任务来给线程池中的线程执行,那么这个时候也不可以让进程销毁,就可以提前创建线程,然后在Java的标准库中就有提取创建线程的方法,下面我们来介绍一下 :

3.2 prestartAllCoreThreads():

public class Demo08 {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
        pool.prestartAllCoreThreads();
        System.out.println("线程创建完毕");
    }
}

因为 prestartAllCoreThreads() 是线程池的一个方法,所以在调用的时候还得向下转型.

在这里插入图片描述

如图所示 : 调用prestartAllCoreThreads() 方法后,线程池中的线程就会全部创建出来

在这里插入图片描述


在线程池中的任务都被执行完,后我们确定接下来不会有任务需要执行了,那么这个时候我们就要来将线程池关闭,以防资源的浪费,在线程池中也有一个可以关闭线程池的方法 :

3.3 shutdown() :

调用shutdown() 的工作原理其实就是,遍历线程池中的工作线程,然后逐个调用 interrupt() 方法,来将线程中断,来起到关闭线程池以防资源浪费的效果.

public class Demo08 {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
        pool.prestartAllCoreThreads();
        System.out.println("线程创建完毕");

        pool.shutdown();
    }
}


调用了 shutdown() 方法后,我们会看到控制台即刻就将打印出程序执行完毕后的日志.

在这里插入图片描述


在Java监视控制台我们也看不到有进程存在了

在这里插入图片描述

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java