多线程与高并发——并发编程(5)

news2024/11/25 6:42:06

文章目录

  • 五、线程池
    • 1 什么是线程池
    • 2 JDK自带的构建线程池的方式
      • 2.1 FixedThreadPool
      • 2.2 SingleThreadExecutor
      • 2.3 CachedThreaPool
      • 2.4 ScheduleThreadPool
      • 2.5 WorkStealingPool
    • 3 ThreadPoolExecutor应用&源码剖析
      • 3.1 为什么要自定义线程池
      • 3.2 ThreadPoolExecutor应用
      • 3.3 ThreadPoolExecutor源码剖析
        • 3.3.1 ThreadPoolExecutor的核心属性
        • 3.3.2 ThreadPoolExecutor的有参构造
        • 3.3.3 ThreadPoolExecutor的execute方法
        • 3.3.4 ThreadPoolExecutor的addWorker方法
        • 3.3.5 ThreadPoolExecutor的Worker工作线程
        • 3.3.6 ThreadPoolExecutor的runWorker方法
        • 3.3.7 ThreadPoolExecutor的getTask方法
        • 3.3.8 ThreadPoolExecutor的关闭方法
      • 3.4 线程池的核心参数设计规则
      • 3.5 线程池处理任务的核心流程
    • 4 ScheduleThreadPoolExecutor应用&源码剖析
      • 4.1 SchedulerThreadPoolExecutor介绍
      • 4.2 ScheduleThreadPoolExecutor应用
      • 4.3 ScheduleThreadPoolExecutor源码剖析
        • 4.3.1 核心属性
        • 4.3.2 schedule方法
        • 4.3.3 At和With方法&任务的run方法

五、线程池

1 什么是线程池

为什么要使用线程池?

  • 在开发中,为了提升效率,我们需要将一些业务采用多线程的方式去执行。比如,有一个比较大的任务,可以将任务分成几块,分别交给几个线程去执行,最终做一个汇总即可。再比如,做业务操作时,需要发送短信或邮件,这些操作也可以基于异步的方式完成,这种异步的方式,其实就是在构建一个线程去执行。
  • 但是,如果每次异步操作或者多线程操作都需要新创建一个线程,使用完毕后,线程再被销毁。这样的话,会对系统造成一些额外的开销。在处理过程中,到底有多少线程处理了多少任务,以及每个线程的开销也无法统计和管理,所以我们需要一个线程池机制来管理这些内容。
  • 线程池的概念和连接池类似,就是在一个Java的集合中存储大量的线程对象,每次需要执行异步操作或者多线程操作时,不需要重新创建线程,直接从集合中拿到线程对象直接执行方法就可以了。
  • JDK中提供了线程池的类。在线程池构建的初期,可以将任务提交到线程池中,会根据一定的机制来异步执行这个任务。
    • 可能任务直接被执行;
    • 任务可以暂存起来,等到有空闲线程再来处理;
    • 任务也可能被拒绝,无法被执行。
  • JDK提供的线程池记录了每个线程处理了多少个任务,以及整个线程池处理了多少个任务。同时还可以针对任务执行前后做一些钩子函数的实现,可以在任务执行前后做一些日志信息,这样可以多记录信息方便后面统计线程池执行任务的一些内容参数等。

2 JDK自带的构建线程池的方式

JDK中基于Executors 提供了很多线程池

2.1 FixedThreadPool

  • 这个线程池的线程数是固定的,在 Executors 中第一个方法就是构建 FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
   
    return new ThreadPoolExecutor(nThreads, nThreads,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>());
}
  • 构建时,需要给newFixedThreadPool方法提供一个 nThreads 的属性,而这个属性其实就是当前线程池中线程的个数。当前线程池的本质其实就是使用 ThreadPoolExecutor。
  • 构建好当前线程池后,线程个数已经固定好(线程是懒加载的,在构建之初,线程并没有构建出来,而是随着任务的提交才会将线程在线程池中构建出来)。如果线程没构建,线程会等着任务执行时被创建和执行;如果线程都已经构建好了,此时任务会被放到 LinkedBlockingQueue 无界队列中存放,等待线程从 LinkedBlockingQueue 中去 take 出任务,然后执行。
  • 测试代码:
public static void main(String[] args) {
   
    ExecutorService threadPool = Executors.newFixedThreadPool(3);
    threadPool.execute(() -> {
   
        System.out.println("1号任务" + Thread.currentThread().getName() + System.currentTimeMillis());
        try {
   
            Thread.sleep(5000);
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    });
    threadPool.execute(() -> {
   
        System.out.println("2号任务" + Thread.currentThread().getName() + System.currentTimeMillis());
        try {
   
            Thread.sleep(5000);
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    });
    threadPool.execute(() -> {
   
        System.out.println("3号任务" + Thread.currentThread().getName() + System.currentTimeMillis());
        try {
   
            Thread.sleep(5000);
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    });
}

2.2 SingleThreadExecutor

  • 这个线程池看名字就知道是单例线程池,线程池中只有一个工作线程在处理任务。如果业务涉及到顺序消费,可以采用 SigleThreadExecutor。
public static ExecutorService newSingleThreadExecutor() {
   
	// 在内部依然是构建了ThreadPoolExecutor,设置线程个数为1。当任务投递过来后,第一个任务会被工作线程处理,后续的任务会被扔到阻塞队列中,投递到阻塞队列中任务的顺序就是工作线程处理的顺序
	// 当前线程池可以用作顺序处理的一些业务中
    return new Executors.FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                    0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>()));
}

static class FinalizableDelegatedExecutorService extends Executors.DelegatedExecutorService {
   
  	// 线程池的使用没有区别,跟正常的ThreadPoolExecutor没区别
	FinalizableDelegatedExecutorService(ExecutorService executor) {
   
        super(executor);
    }
	// finalize是当前对象被GC干掉之前要执行的方法
	// 当前FinalizableDelegatedExecutorService的目的是为了在当前线程池被GC回收之前,可以执行shutdown,shutdown方法是将当前线程池停止,并且干掉工作线程
	// 但是不能基于这种方式保证线程池一定会执行shutdown,finalize在执行时,是守护线程,这种线程无法保证一定可以执行完毕。
	// 在使用线程池时,如果线程池是基于一个业务构建的,在使用完毕之后,一定要手动执行shutdown,否则会造成JVM中一堆线程
    protected void finalize() {
   
        super.shutdown();
    }
}
  • 测试代码:
public static void main(String[] args) {
   
    ExecutorService threadPool = Executors.newSingleThreadExecutor();

    threadPool.execute(() -> {
   
        System.out.println(Thread.currentThread().getName() + "," + "111");
    });
    threadPool.execute(() -> {
   
        System.out.println(Thread.currentThread().getName() + "," + "222");
    });
    threadPool.execute(() -> {
   
        System.out.println(Thread.currentThread().getName() + "," + "333");
    });
    threadPool.execute(() -> {
   
        System.out.println(Thread.currentThread().getName() + "," + "444");
    });
}
  • 测试线程池使用完毕后,不执行 shutdown 的后果:
    • 如果是局部变量,仅限当前线程使用的线程池,在使用完毕之后要记得执行 shutdown,避免线程无法结束。
    • 如果是全局的线程池,很多业务都会用到,使用完毕之后不用 shutdown,因为其他业务也要执行当前线程池。

image.png

2.3 CachedThreaPool

  • 看名字好像是一个缓存的线程池
public static ExecutorService newCachedThreadPool() {
   
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
            60L, TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>());
}
  • 当第一次提交任务到线程池时,会直接构建一个工作线程,这个工作线程执行完之后,如果60秒没有任务可以执行,那么会结束;如果等待 60 秒期间有任务进来,它会再次拿到这个任务去执行。如果后续提升任务时,没有线程是空闲的,那么久构建工作线程去执行。
  • 最大的一个特点:任务只要提交到当前的 CachedThreaPool 中,就必然有工作线程可以处理。
  • 测试代码:
public static void main(String[] args) {
   
    ExecutorService threadPool = Executors.newCachedThreadPool();
    for (int i = 1; i <= 200; i++) {
   
        final int x = i;
        threadPool.execute(() -> {
   
            try {
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":" + x);
        });
    }
}

2.4 ScheduleThreadPool

  • 看名字可以猜到当前线程池是一个定时任务的线程池,而这个线程池就是可以以一定周期去执行一个任务,或者延迟多久执行一个任务一次。
// 基于这个方法可以看到,构建的是 ScheduledThreadPoolExecutor
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
   
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService {
   
	// ...
}
  • 本质上还是ThreadPoolExecutor,只不是在原来的线程池基础上实现了定时任务的功能。原理是基于 DelayQueue 实现的延迟执行,周期性执行是任务执行完毕后,再次扔回到阻塞队列中。
  • 测试代码:
public static void main(String[] args) {
   
    ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
//        // 正常执行
//        threadPool.execute(() -> {
   
//            System.out.println(Thread.currentThread().getName() + ": 1");
//        });
//
//        // 延迟执行,执行当前任务延迟5s后执行
//        threadPool.schedule(() -> {
   
//            System.out.println(Thread.currentThread().getName() + ": 2");
//        }, 5, TimeUnit.SECONDS);
//
//        // 周期执行,当前任务第一次延迟2s执行,然后每1s执行一次
//        // 这个方法在计算下次执行时间时,是从任务刚刚开始时就计算
//        threadPool.scheduleAtFixedRate(() -> {
   
//            try {
   
//                Thread.sleep(3000);
//            } catch (InterruptedException e) {
   
//                e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName() + ": 3");
//        }, 2, 1, TimeUnit.SECONDS);

    threadPool.scheduleWithFixedDelay(() -> {
   
        try {
   
            Thread.sleep(3000);
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ": 4");
    }, 2, 1, TimeUnit.SECONDS);
}

2.5 WorkStealingPool

  • 当前JDK提供的构建线程池的方式 newWorkStealingPool 和之前的线程池有非常大的区别,之前定长、单例、缓存、定时任务都是基于 ThreadPoolExecutor 去实现的,而 WorkStealingPool 是基于 ForkJoinPool 构建的。
  • ThreadPoolExecutor的核心点:只有一个阻塞队列存放当前任务
  • ForkJoinPool的核心点:当有一个特别大的任务时,采用 ThreadPoolExecutor 只会有一个线程去执行,但是 ForkJoinPool 会将一个大任务拆分成很大小任务,放到当前线程的阻塞队列中,其它空闲线程就可以去处理有任务的线程的阻塞队列中的任务

image.png

  • 核心就是希望没有工作线程处于空闲状态,每个线程都使用率满满

举个例子:来一个比较大的数组,里面存满值,计算总和

  • 单线程处理:
/** 非常大的数组 */
static int[] nums = new int[1_000_000_000];
// 填充值
static{
   
    for (int i = 0; i < nums.length; i++) {
   
        nums[i] = (int) ((Math.random()) * 1000);
    }
}
public static void main(String[] args) {
   
    // ===================单线程累加10亿数据================================
    System.out.println("单线程计算数组总和!");
    long start = System.nanoTime();
    int sum = 0;
    for (int num : nums) {
   
        sum += num;
    }
    long end = System.nanoTime();
    System.out.println("单线程运算结果为:" + sum + ",计算时间为:" + (end  - start));
}
  • 多线程分而治之方式处理:
/**
 * 非常大的数组
 */
static int[] nums = new int[1_000_000_000];
// 填充值
static {
   
    for (int i = 0; i < nums.length; i++) {
   
        nums[i] = (int) ((Math.random()) * 1000);
    }
}
public static void main(String[] args) {
   
    // ===================单线程累加10亿数据================================
    System.out.println("单线程计算数组总和!");
    long start = System.nanoTime

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/993662.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据透视表如何让多个行标签并列显示?

数据透视表如何让多个行标签并列显示&#xff1f; “数据透视表工具 - 报表布局" 这里有三种格式&#xff1a;&#xff08;1&#xff09;以压缩形式显示&#xff1b;&#xff08;2&#xff09;以大纲&#xff1b;&#xff08;3&#xff09;以表格形式。 选择“以表格显…

轻量容器引擎Docker基础使用

轻量容器引擎Docker Docker是什么 Docker 是一个开源项目&#xff0c;诞生于 2013 年初&#xff0c;最初是 dotCloud 公司内部的一个业余项目。 它基于 Google 公司推出的 Go 语言实现&#xff0c;项目后来加入了 Linux 基金会&#xff0c;遵从了 Apache 2.0 协议&#xff0c;…

MySQL-DDL语句

MySQL-DDL语句 数据库操作语句增删数据库查看数据库列表创建数据库进入&#xff08;使用&#xff09;数据库/查看当前所在的数据库查看数据库的建库语句查看数据库的编码集和校验集删除数据库修改数据库的编码集查看数据库支持的编码集和校验集 数据库备份备份单个数据库恢复数…

基于spring boot+ vue+ mysql开发的UWB室内外定位系统源码

现代制造业厂区面积大、人员数量多、物资设备不断增加&#xff0c;随着工业信息化技术的发展&#xff0c;大型制造企业中对人员、车辆、物资的管理要求越来越细致。 高精度定位管理系统使用UWB室内定位技术&#xff0c;通过在厂区安装定位基站&#xff0c;为人员或设备佩戴定位…

djanjo安装-各版本对应表

djanjo安装-各版本对应表 1 各版本对应表 django详细版本与python版本对照表&#xff0c;尽量按照表中的版本对应安装 2 djanjo安装 pip install django2.2.* // 默认会安装:Django2.2的最新版本2.2.28和对应的sqlparse-0.4.4、pytz等包python -m django --version // 检测…

抖店的产品是怎么卖出去的?带你了解抖店的核心玩法,不需要直播

我是王路飞。 你认为抖店就是在抖音开直播&#xff0c;然后把产品吆喝着卖出去吗&#xff1f; 如果你是这样看待抖店的话&#xff0c;那只能说明你根本不了解抖音小店&#xff0c;也不了解抖音的带货模式。 今天就给你们讲一下抖店的产品都是怎么卖出去的。 带你了解抖店的…

【Terraform】Terraform自动创建云服务器脚本

Terraform 是由 HashiCorp 创建的开源“基础架构即代码”工具 &#xff08;IaC&#xff09; 使用HCL&#xff08;配置语言&#xff09;描述云平台基础设施&#xff08;这里教你使用低级基础设施&#xff1a;交换机、云服务器、VPC、带宽&#xff09; Terraform提供者&#xf…

SpringBoot原理-自动配置-方案

自动配置原理 在运行SpringBoot项目启动类&#xff08;SpringBootApplication标注启动类&#xff09;启动SpringBoot项目时&#xff0c;SpringBootApplication是一个混合注解&#xff0c;包括 SpringBootConfiguration&#xff08;&#xff09;标识该类是一个配置类&#xff0…

SpringMVC:从入门到精通,7篇系列篇带你全面掌握--四.5分钟搞定文件上传与下载

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于SpringMVC的相关操作吧 需要添加的依赖 <dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</a…

vscode开发油猴插件环境配置指南

文章目录 一、环境配置1.1油猴插件开始编写代码1.2油猴插件配置1.2.1浏览器插件权限1.2.2插件自身权限 2. 油猴脚本API学习2.1 头文件2.2 油猴API 一、环境配置 1.1油猴插件开始编写代码 在vscode 中写入如下代码‘ // UserScript // name cds_test // namespace …

C++ if 语句

一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。 语法 C 中 if 语句的语法&#xff1a; if(boolean_expression) {// 如果布尔表达式为真将执行的语句 }如果布尔表达式为 true&#xff0c;则 if 语句内的代码块将被执行。如果布尔表达式为 false&#xff0c;则 if 语…

Y=AX+B问题

记得数学还是自己喜欢的科目&#xff0c;可熟人发了这么一个求解&#xff0c;要求代码实现。结果把自己整的不会了。 参看他人文档“”YAXB推导求解 以及ppt&#xff1a;中科院的线性代数ppt 1 大家熟悉的拟合法 2矩阵法 学会了吧&#xff1f;&#xff1f; 3 例题 原图来源…

Linux信号量环形队列处理生产消费者模型

目录 一、信号量 1.1 信号量的概念 1.2 信号量的函数接口 1.2.1 sem_init&#xff08;&#xff09; 1.2.2 sem_destroy&#xff08;&#xff09; 1.2.3 sem_wait&#xff08;&#xff09;(P操作申请信号量) 1.2.4 sem_post&#xff08;&#xff09;(V操作增加信号量) 二…

【爬虫】8.1. 深度使用tesseract-OCR技术识别图形验证码

深度使用tesseract-OCR技术识别图形验证码 文章目录 深度使用tesseract-OCR技术识别图形验证码1. OCR技术2. 准备工作3. 简单作用了解3.1. 验证码图片爬取-screenshot_as_png3.2. 识别测试-image_to_string3.2.1. 正确识别3.2.2. 错误识别3.2.3. 灰度调节 3.3. 识别实战-使用im…

qsort 函数的使用

一、qsort 函数的形式 1.1使用 qsort 函数包含的库 1.2qsort 函数的参数 qsort&#xff1a;对数组的元素进行排序 1.3参数中的 compar 函数 struct stu {char name[20];//姓名int age;//年龄double grade;//成绩 }; int cmp_name(void* p1, void* p2) {//如果按照姓名排序ret…

个人信息去标识化具体实施指南

声明 本文是学习个人信息去标识化指南. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 范围 本标准描述了个人信息去标识化的目标和原则&#xff0c;提出了去标识化过程和管理措施。 本标准针对微数据提供具体的个人信息去标识化指导&#xff0c;适…

windows 查询正在运行的系统

查询当前哪些服务在运行 net start 下图为查询到的正在运行的服务 查看某个服务是否正在运行 方式1 net start |find "MySQL" 出现下图说明mysql服务正在运行 方式2 sc query mysql 出现下图说明服务正在运行

打字侠:一款专业的中文打字网站

打字侠第一个正式版发布啦&#xff01;&#xff01;&#xff01; 虽然离期望的样子还有一段路要走&#xff0c;不过能看到它正式发布&#xff0c;我还是很激动哟&#xff01; 打字侠是一款面向中学生和大学生的在线打字软件&#xff0c;它通过合理的课程设计和精美的图形界面帮…

删除linux(centos7)系统自带的open jdk,安装配置jdk环境

查看jdk版本 安装的linux自带jdk8版本&#xff0c;我们不用自带的。 安装jdk步骤 1、下载 下载地址&#xff1a;https://www.oracle.com/java/technologies/downloads 2、创建目录 创建文件夹&#xff0c;用来部署JDK&#xff0c;将JDK安装部署到&#xff1a;/export/se…

【技术支持案例】S32K146的hard fault问题处理

文章目录 1. 案例背景2. 方案准备2.1 HardFault&#xff08;硬件错误异常&#xff09;2.2 UsageFault&#xff08;用法错误异常&#xff09;2.3 BusFault&#xff08;总线错误异常&#xff09;2.4 MemManage Fault&#xff08;存储器管理错误异常&#xff09; 3. 现场支持3.1 现…