我的编程空间,编程开发者的网络收藏夹
学习永远不晚

【Java | 多线程案例】——初识线程池

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

【Java | 多线程案例】——初识线程池

个人主页:兜里有颗棉花糖
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创
收录于专栏【Java系列专栏】【JaveEE学习专栏】
本专栏旨在分享学习JavaEE的一点学习心得,欢迎大家在评论区交流讨论💌

这里写目录标题

  • 一、线程池概念
  • 二、线程池的创建和使用
    • 如何创建线程池
    • 如何使用线程池
  • 三、ThreadPoolExecutor类
  • 四、线程池的简单实现
    • 补充
  • 五、总结

一、线程池概念

在一些场景中我们需要频繁的创建和销毁线程(这样的话就会有很大的成本开销),所以我们可以使用线程池提前创建好一些线程,当我们后续需要使用某个线程的时候,我们直接从线程池的池子里拿这个线程就可以,相当于从线程池中获取到现有的线程)的方式来节约频繁创建和销毁线程所带来的成本。

现在来看一个问题:为什么从线程池取线程要比从系统中创建线程更加高效呢?
如果从操作系统系统这里创建线程,需要调用系统api,然后进一步的由操作系统内核来完成线程的创建。由于操作系统是为所有的进程提供服务的,所以线程什么时候创建好其实是不可控的。
如果是从线程池这里获取线程的话(这种方式是可控的),上面创建线程的过程都已经操作好了,此时就是一个去线程的过程。简单来说,当我们从线程池获取线程时,实际上是从线程池中获取一个预先创建的空闲线程,而不是动态地创建新线程。

当然,Java中提供了现成的线程池来供我们使用。

二、线程池的创建和使用

如何创建线程池

  • ExecutorService service = Executors.newFixedThreadPool(4);

在这里插入图片描述
下面是创建线程池的四种方法:

  • Executors.newFixedThreadPool(4):这是创建固定线程数量的线程池
  • Executors.newCachedThreadPool():创建动态数目变化的线程池
  • Executors.newSingleThreadExecutor():创建包含单个线程的线程池(即只包含一个线程),这种创建线程的方式比原生创建线程的api更简单一些
  • Executors.newScheduledThreadPool():创建一个具备定时执行任务能力的线程池,它会在指定的时间间隔内周期性地执行任务。类似于定时器的效果,可以添加一些任务,这些任务会在后续的某个时候进行执行,这些任务在执行的时候并不是只有一个扫描线程在执行任务,而是可能由多个线程共同执行所有的任务(比如说这里有50个任务,那么这50个任务就会由这些扫描线程共同来完成执行这50个任务)。

上述4种方法创建的是4种不同类型的线程池,当然除了上面4种线程池外,Java标准库中还提供了一个接口更为丰富的线程池类ThreadPoolExecutor。这里解释一下,上面介绍的4种线程池其实是对原生类ThreadPoolExecutor进行了封装而得到的4种线程池,这4种线程池只是为了我们使用方便所以才对ThreadPoolExecutor类进行了封装。如果我们这四种线程池不能够很好的解决我们当下的问题的话我们就可以使用原生的线程池类ThreadPoolExecutor(因为此类可以提供我们更多可调整的选项来供我们使用以便更好的解决我们的实际需求)。

如何使用线程池

上面线程池对象创建好了之后,现在我们来使用线程池对象,我们可以通过submit()方法将任务添加到线程池中。

service.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println("hell world!!!");
    }
});

示例线程池代码如下:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo24 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(4);
        for(int i = 1;i <= 10;i++) {
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hell world!!!");
                }
            });
        }
    }
}

运行结果如下:
在这里插入图片描述
解释:创建一个固定大小为 4 的线程池,并向线程池中提交 10 个任务,任务是打印一句话 “hello world!!!”。
当任务执行完毕后,线程会自动返回到线程池中,此时线程池中依然有空闲线程,可以继续执行任务。

现在来总结一下如何使用线程池:

  • 先创建线程池对象示例。
  • 调用submit()方法来添加任务。

三、ThreadPoolExecutor类

我们现在来看一下这个类的构造方法,请看:
在这里插入图片描述

这里对上图种ThreadPoolExecutor类的最后一个构造函数,也就是参数最多最复杂的构造函数进行解释。
首先我们要知道ThreadPoolExecutor类中的线程个数是会根据当前任务的情况发生动态变化的。

  • corePoolSize:核心线程数,即线程池最少要有这些数量的线程数。
  • maximumPoolSize:最大线程数,即线程池中最多只能包含这些数量的线程数。
    以上两个参数可以保证在繁忙的时候可以高效的处理任务,也可以保证在空闲的时候不会浪费资源。
  • long keepAliveTime, TimeUnit unit:是用来设置线程池中空闲线程的存活时间的参数,前者表示时间的数值,后者表示时间的单位(比如毫秒或者秒)。当空闲线程在这个时间之内没有任务需要执行的话,这些空闲线程就会自动销毁。
  • BlockingQueue workQueue:线程池内有很多任务,这些任务可以使用阻塞队列进行管理。线程池可以内置阻塞队列,当然也可以手动指定一个阻塞队列。
  • ThreadFactory threadFactory:通过这个工厂类来按照不同的方式创建线程。
  • RejectedExecutionHandler handler:当线程池中的阻塞队列满了之后,此时继续添加任务,针对这个新添加的任务的应对方式或者执行策略,即此时我们可以手动指定应对策略来处理这个新添加的任务(在Java标准库中已经为我们提供好了现成的应对策略,如下:)。
  • 在这里插入图片描述
  • 应对策略1: ThreadPoolExecutor.AbortPolicy:这里针对新添加的任务会直接抛出异常,此时线程池停止执行任务(我们可以理解为罢工开摆)。
  • 应对策略2:ThreadPoolExecutor.CallerRunsPolicy:谁是添加这个任务的线程就由谁来执行这个任务(简单来说就是调用者去执行)。
  • 应对策略3:ThreadPoolExecutor.DiscardOldestPolicy:丢弃最早的任务来去执行新的任务。
  • 应对策略4:ThreadPoolExecutor.DiscardPolicy:这里会直接丢弃掉新添加的任务。

四、线程池的简单实现

代码如下:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

class MyThreadPool {
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();

    // 通过这个submit方法将任务添加到线程池中
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }

    // n表示线程池有几个线程
    public MyThreadPool(int n) {
        for(int i = 1;i <= n;i++) {
            Thread t = new Thread(() -> {
                while(true) {
                    try {
                        // 取出任务并执行
                        Runnable runnable = queue.take();
                        runnable.run();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            t.start();
        }
    }
}
public class Demo25 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool myThreadPool = new MyThreadPool(4);
        for(int i = 1;i <= 1000;i++) {
            myThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " hell world");
                }
            });
        }
    }
}

运行结果如下:
在这里插入图片描述

补充

这里我们补充一点,线程池中的线程个数是如何设置的,具体应该设置为多少合适呢?

首先我们要只要不同的线程要完成的工作往往是不一样的。

  • 有的线程的工作属于CPU密集型:主要负责运算,即这种CPU密集型的工作大部分都是需要在CPU核心上去进行工作的,CPU需要给这些工作安排核心才可以让需要完成的工作有所进展。举例:如果CPU是N个核心的话,当线程数量为N的时候,理想状态下每个CPU核心都会被安排到一个线程,如果此时再想添加更多的线程的话,这些新添加的线程就会进行排队等待状态(即不会有新的进展),这种情况下我们再怎么增加线程也不会有新的进展了,即增加线程数量反而会让调度调度开销变大从而影响到线程的执行效率。所以CPU密集型的任务不应该让线程数超过CPU核心数
  • 有的线程的工作属于IO密集型:比如读写文件、用户输入、网络通信等涉及到大量的等待时间。等待过程中并没有使用CPU,这样的线程即使更多一些也不会给CPU造成太大的负担。所以,当线程属于IO密集型的时候,如果CPU核心数是16,此时如果我们设置的线程数量是32的话也是没有问题的(因此的大部分的线程都是在等待过程,甚至是CPU的占用率还很低)

当然在实际的开发过程中,一部分的线程工作是CPU密集型的,一部分的线程工作是IO密集型的。此时我们其实是很难去量化CPU密集型和IO密集型的比例的。换句话说一个线程几成是在CPU上运行,几成是在等待IO都是不确定的。我们最好是通过实验的方式来找到最合适的线程数

五、总结

上述的线程池分为两组:一组线程池是对ThreadPoolExecutor类进行的分装;另一组线程池是就是原生ThreadPoolExecutor类了。具体使用哪一组的哪一种线程池需要我们根据具体场景来进行选择。

关于线程池中线程的数量问题我们可以通过性能测试,即尝试不同的线程数目来找到性能和系统资源开销比较均衡的线程数量。

本文到这里就结束了,希望友友们可以支持一下一键三连哈。嗯,就到这里吧,再见啦!!!

在这里插入图片描述

来源地址:https://blog.csdn.net/m0_74352571/article/details/135315245

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

【Java | 多线程案例】——初识线程池

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

多线程案例(4)-线程池

文章目录 多线程案例四四、线程池 大家好,我是晓星航。今天为大家带来的是 多线程案例-线程池 相关的讲解!😀 多线程案例四 四、线程池 线程池是什么 虽然创建线程 / 销毁线程 的开销 想象这么一个场景:
2023-08-19

Java多线程之线程池七个参数的示例分析

这篇文章主要介绍Java多线程之线程池七个参数的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!ThreadPoolExecutor是JDK中的线程池实现,这个类实现了一个线程池需要的各个方法,它提供了任务提交
2023-06-14

多线程编程(3):线程池ThreadPo

在面向对象编程中,经常会面对创建对象和销毁对象的情况,如果不正确处理的话,在短时间内创建大量对象然后执行简单处理之后又要销毁这些刚刚建立的对象,这是一个非常消耗性能的低效行为,所以很多面向对象语言中在内部使用对象池来处理这种情况,以提高性能
2023-01-31

java怎么使用线程池启动多线程

在 Java 中,可以使用线程池来启动多线程。以下是使用线程池启动多线程的示例代码:首先,需要导入 `java.util.concurrent.ExecutorService` 和 `java.util.concurrent.Executo
2023-09-15

Java线程池中多余的线程怎么回收

这篇文章给大家分享的是有关Java线程池中多余的线程怎么回收的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。那么,就以JDK1.8为例分析吧。1.runWorker(Worker w)工作线程启动后,就进入runW
2023-06-15

Java线程池知识点有哪些

这篇文章将为大家详细讲解有关Java线程池知识点有哪些,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。1、线程数使用开发规约阿里巴巴开发手册中关于线程和线程池的使用有如下三条强制规约【强制】创建线程或线程池
2023-06-29

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录