多线程案例-线程池

news2024/11/19 10:24:50

1.什么是线程池

线程存在的意义是当使用进程进行并发编程太重了,此时引入了一个"轻量级的"进程-线程.

创建线程比创建进程更高效,销毁线程比销毁进程更高效,调度线程比调度进程更高效..此时我们就用多线程来代替进程进行并发编程了,但是随着对性能的要求的提高,线程相对来说又变"重"了,在我们频繁创建和销毁线程是,也有很大的开销

因此我们为了进一步提高效率,提出了两种办法

第一种:类似于进程和线程,我们创建一个"轻量级线程"-协程/纤程(这种方法还没有加入到java标准库中)
第二种:使用线程池,来降低创建/销毁线程带来的开销
线程池就是和字符串常量池,数据库连接池相同的道理,事先把需要用的线程创建好,放到线程池中,后面需要使用的时候,直接从池中获取,如果用完了,就还给线程池..这两个操作是比创建线程/销毁线程要更加高效的!!

线程池的好处:减少每次启动,销毁线程的损耗

创建线程/销毁线程是交由操作系统内核完成的,从线程池获取/交还是由用户代码能实现的,不必交给操作系统内核

相比于操作系统内核来说,用户态程序的执行更为可控,想要执行某个任务(从线程池取线程,还线程)会很快的就完成了,如果通过操作系统内核创建线程,销毁线程,需要通过系统调用,让内核来执行,但是内核身上背负的是整个计算机的活动,服务的是所有应用程序,整体过程是"不可控的",因此使用线程池更为高效!

2.标准库中的线程池

在Java标准库中也提供了线程池可以直接使用

标准库中提供了很多种创建线程池的方式,这里提供一种简单的方式创建线程池

线程池里的线程数目是十个

我们在上述代码中并没看见new关键字,此处的new是方法名字的new,不是关键字

这里相当于直接使用类的静态方法创建出的对象,相当于把new操作给隐藏到这样的方法后面了.这种方法就成为"工厂方法",提供这个方法的类,称为"工厂类",此处这个代码使用的是"工厂设计模式",下来我们了解一下什么是"工厂模式"

工厂模式

工厂模式:使用普通的方法来代替构造方法,创建对象(构造方法只能构造一种对象,如果要构造不同情况的对象,就很难了)

举个例子:我们要使用xy直角坐标系和ra极坐标系确定一个点时,要创造两种不同的对象

class Point{
    public Point(double x,double y) {
    }
    public Point(double r,double a) {
    }
}

很明显这个代码是有问题的,编译器报错,正常来说,多个构造方法是"重载"的方式来提供的,重载要求:方法名相同,参数的个数或者类型不相同,为了解决这个问题,可以使用工厂模式

class Point{
    public static Point makePointByXY(double x,double y) {
        
    }
    public static Point makePointByRA(double r,double a) {
    }
}

此时我们就可以使用普通方法来创建对象,因此多种方法构造,直接使用不同的方法名就行,参数就不用再区分了!


我们继续说线程池部分的知识

构建好十个线程后我们就可以利用线程来完成任务,线程池提供了一个重要的方法-submit,可以给线程池提供若干个任务.

public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(10);
        pool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        });
    }

运行程序我们发现:主线程结束了,整个进程还没有结束,线程池中的线程都是前台线程,此时会阻止进程结束(定时器也是如此)

我们创建多个任务提交

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

这1000个任务就是由十个线程一起完成的,差不多是一个线程执行一百个,不是严格的一个一百,由于每个任务时间差不多,因此每个线程执行的数量也差不多.这个操作类似于排队做核酸,核酸点人数都差不多,做核酸速度也差不多,因此相同时间大概完成的数量是相同的!

lamada表达式的变量捕获

还有个问题,能不能不定义n

实际上是不行的,会出现这个错误:

这个语法规则和lamada表达式的变量捕获有关.

此处的run方法属于Runnable.这个方法的执行时机不是立即执行,而是在未来的某个时间节点上(后续的线程池队列中,排队到他了就去执行)

变量i是主线程 的局部变量,随着主线程执行结束,他就销毁了,很可能主线程的for循环执行完了,当前run的任务在线程池中没排到,这是i已经销毁了

为了避免执行run的时候i已经销毁,于是就有了变量捕获,也就是让run方法把刚才的主线程的i给当前run的栈上拷贝一份(在定义run的时候偷偷记住一分i,后续执行run的时候,也创建一个i的局部变量,并且赋值给n)

这个过程称为变量捕获,是为了抹平生命周期的差异,i本来跟着主线程的生命周期走,但run方法实际在主线程生命周期之外还需要使用i,所以需要进行一份拷贝

在Java中,对变量捕获作了一些额外的要求,JDK1.8之前,要求只能捕获final修饰的变量,1.8开始.放松了标准,只要代码中没有修改这个变量,也可以捕获.此处i是有修改的,但是n没有修改可以捕获


Executors这个工厂类给我们提供了很多种风格的线程池

Executors.newCachedThreadPool()

这个线程池的线程数量是动态可变的,如果任务多了,就多增加几个线程,线程少了,就少创建几个线程

Executors.newSingleThreadExecutor()

这个线程池里只有一个线程,用的比较少

Executors.newScheduledThreadPool()

类似于定时器,也是让任务延时运行,只不过执行的时候不是由扫描线程自己执行,而是由单独的线程池来执行

这几种线程池本质都是通过包装ThreadPoolExexutor来实现的

这个线程池用起来比较麻烦,所以才提供了工厂类,让我们使用起来简便,这也意味着它本身的功能更强大


ThreadPoolExexutor类

java.util.concurrent这个包中的很多类都是和并发编程(多线程编程)密切相关的,也成为JUC

打开这个包,找到

找到该类提供的构造方法

第四个版本参数,方法是最多的.我们逐个分析

int corePoolSize

这个参数是核心线程数

int maximumPoolSize

这个参数是最大线程数

ThreadPoolExexutor相当于把里面的线程分为两类

一类是正式工(核心线程数),一类是临时工,两个加起来就是最大行程数

允许正式员工摸鱼,临时工不能摸鱼,临时工摸鱼就会被开除(销毁)!!

如果任务很多,那么我们就要更多的线程来完成,但一个程序的任务有时多有时少,少时我们需要对现有的线程进行一定的销毁..原则时正式工(核心线程)留着,临时工动态调节!实际开发时线程池中的线程设置为多少(N(cpu核数),N+1,1.5N,2N....)是不准确的,面试遇到,回答具体数字那么肯定是答错了..

对不同的程序,需要设置的线程数也是不同的

需要考虑两个极端情况:
CPU密集型
每个线程执行的任务都是需要使用CPU的,此时线程池线程数最多不应该超过CPU核数,设置的更大也无效.因为cpu就那么多
IO密集型
每个线程干的工作就是等待IO(读写硬盘,读写网卡,等待用户输入....),不多占用CPU,此时线程处于阻塞状态,不参与调度,这是线程数多一点也没事,不再受制于CPU的核数了.

然而实际开发种.往往一部分吃cpu,一部分等待IO,所以要在实践中确定线程数量,通过测试/实验来确定!!

long keepAliveTime, TimeUnit unit,

这两个参数描述了临时工摸鱼的最大时间...如果超过这个时间,线程就被销毁了!

BlockingQueue<Runnable> workQueue

这个是线程池的任务队列.此处使用阻塞队列,若没有任务,就阻塞,有就take成功...

ThreadFactory threadFactory

用于线程池创建线程

 RejectedExecutionHandler handler

描述了线程池的"拒绝策略"..也是一个特殊的对象,描述了当线程池任务队列满了,如果继续添加任务会有什么行为

看看标准库提供的拒绝策略:

第一种:如果任务太多,队列满了,直接抛出异常

第二种:如果队列满了,多出来的任务,谁添加的谁负责执行

第三种:如果队列满了,丢弃最早的任务

第四种:丢弃最新的任务

针对ThreadPoolExexutor的参数的解释是高频的面试题,需要牢记

3.实现线程池

一个线程池,要有一个阻塞队列,保存任务.还要有若干个工作线程

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

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool myThreadPool = new MyThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            int n = i;
            myThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello"+n);
                }
            });
        }
    }
}
class MyThreadPool{
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
    //n表示线程数
    public MyThreadPool(int n){
        for (int i = 0; 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 void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
}

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

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

相关文章

大数据必学Java基础(一百一十八):什么是Maven和它的下载整合

文章目录 什么是Maven和它的下载整合 一、什么是Maven 二、IDEA默认整合了Maven 三、下载地址

【Linux】RHEL8 中nmcli使用,必备!

redhat8中nmcli日常使用 第 2 章 配置以太网连接 Red Hat Enterprise Linux 为管理员提供不同的选项来配置以太网连接。例如&#xff1a; 在命令行中使用 nmcli 配置连接。使用 nmtui 在基于文本的用户界面中配置连接。使用 RHEL 系统角色在一个或多个主机上自动配置连接。使…

电脑小问题:定时关机的设置

设置定时关机生活中&#xff0c;我们有时候需要对电脑进行定时关机。那么&#xff0c;如何设置定时关机呢&#xff1f;步骤如下&#xff1a; 1. 按 win R &#xff0c;弹出命令窗口&#xff0c;输入 taskschd.msc &#xff0c;点击确定。 2. 弹出任务计划程序窗口&#xff0c;…

使用ResNet34实现CIFAR10数据集的训练

如果对你有用的话&#xff0c;希望能够点赞支持一下&#xff0c;这样我就能有更多的动力更新更多的学习笔记了。&#x1f604;&#x1f604; 使用ResNet进行CIFAR-10数据集进行测试&#xff0c;这里使用的是将CIFAR-10数据集的分辨率扩大到32X32&#xff0c;因为算力相关的…

摘要/哈希/散列算法MD5 SHA1 SHA256 SHA512的区别和MAC算法

一、摘要算法大致都要经过以下步骤 1. 明文数据预处理 1.1 填充比特 MD5、SHA1、SHA256 的分组长度都是512bit 需要填充比特使其位长对512求余的结果等于448 SHA512 的分组长度是 1024bit 需要填充比特使其对1024求余的结果等于896 相同&am…

ECharts基本使用

文章目录Echarts概述Echarts初体验ECharts基础配置Echarts社区介绍Echarts-map使用Echarts概述 常见的数据可视化库&#xff1a; D3.js 目前 Web 端评价最高的 Javascript 可视化工具库(入手难)ECharts.js 百度出品的一个开源 Javascript 数据可视化库Highcharts.js 国外的前…

项目合并后,font字体资源被替换导致TextMeshPro不能显示文字,抢救方法

一&#xff0c;字体消失 项目合并时&#xff0c;因为资源更替&#xff0c;导致TextMeshPro不能找到自己原来使用的font资源&#xff0c;以致不能显示文字。 二、抢救方式 1、找到所有用到TextMeshPro的物体2、把他们的字体重新设置成你要的字体 关键步骤&#xff1a; 1、找…

赛事推荐| 建筑物实例分割和高度估计的多任务学习——2023 IEEE GRSS 数据融合赛道2

1. 赛题名称 联合建筑物提取和高度估计的多任务学习 2. 赛题背景 该轨道定义了建筑物提取和高度估计的联合任务。两者都是建筑改造的两个非常基础和必不可少的任务。与轨道 1 相同&#xff0c;输入数据是多模态光学和 SAR 卫星图像。单视图卫星图像中的建筑物提取和高度估计…

记录redis连接被打满的踩坑之路

一、系统异常现象系统有一个功能向别的系统多线程推送用户数据信息&#xff0c;前几天发现该推送功能报内部错误&#xff0c;经过查看后台日志文件&#xff0c;发现org.redisson.client.RedisConnectionException: Unable to connect to Redis server:&#xff0c;io.netty.cha…

使用docker训练yolov5

使用docker训练yolov5 配置docker&#xff0c;配置的好处是docker中的环境或者说容器坏了不影响主机&#xff0c;并且可以减少配置环境的时间和精力 sudo apt update sudo apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common # c…

Docker 部署SQL Server 2017

Docker 部署SQL Server 2017 Docker部署 registry Docker搭建 svn Docker部署 Harbor Docker 部署SQL Server 2017 Docker 安装 MS SqlServer Docker部署 Oracle12c 文章目录Docker 部署SQL Server 2017一、部署步骤1.下载镜像2.创建容器并运行二、参考文档一、部署步骤 1.下…

Unity 之 资源加载 -- 可寻址系统概念介绍 -- 入门(一)

可寻址系统面板概念 -- 入门&#xff08;一&#xff09;一&#xff0c;可寻址系统概念介绍1.1 官方话术1.2 几个概念二&#xff0c;可寻址系统目录介绍2.1 导入工程2.2 目录介绍概述&#xff1a;本片文章带大家了解可寻址系统的相关概念&#xff0c;为大家介绍可寻址系统导入方…

生成数据分析报告pandas_profiling.ProfileReport

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 生成数据分析报告 pandas_profiling.ProfileReport 选择题 对于以下python代码表述错误的一项是? import pandas as pd import pandas_profiling as pp dfpd.DataFrame({ a:[23,18,21], b:[…

excel数据核对技巧:如何用函数公式标识输入正误

我们平时人工录入较长的文本数据时&#xff0c;稍不注意就容易出错。为了避免出错&#xff0c;通常我们会提前对单元格设置数据验证。有些时候&#xff0c;我们还会考虑列与列之间的关系&#xff0c;根据列关系自动判定数据的对错。比如下表,款号、货号、色号、条码的信息均存在…

【MySQL进阶教程】InnoDB引擎

前言 本文为 【MySQL进阶教程】InnoDB引擎 相关知识&#xff0c;下边将对InnoDB引擎介绍&#xff0c;InnoDB引擎架构&#xff0c;事务原理&#xff0c;MVCC等进行详尽介绍~ &#x1f4cc;博主主页&#xff1a;小新要变强 的主页 &#x1f449;Java全栈学习路线可参考&#xff…

获取每年的周数据 第几周 开始日及结束日 思路

public static void main(String[] args) {int year 2023;SimpleDateFormat simpleDateFormat new SimpleDateFormat("yyyy-MM-dd");while (true) {int weekValue 1;Calendar calendar new GregorianCalendar();//***踩坑 // calendar.setFirstDayOfW…

冒泡排序终极版(模拟qsort)

目录 普通版冒泡排序 qosrt函数 终极版冒泡排序 终极版冒泡排序整体测试代码 普通版冒泡排序 冒泡排序想必大家都很了解了吧&#xff0c;冒泡排序的算法思想就是两两比大小&#xff0c;一轮一轮比&#xff0c;每比完一轮排出一个数字的顺序&#xff0c;那就让我们先来看一…

软件测试/测试开发丨从 0 开始学 Python 自动化测试开发(二):环境搭建

本文是「从 0 开始学 Python 自动化测试开发」专题系列文章第二篇 —— 环境搭建篇&#xff0c;适合零基础入门的同学。没有阅读过上一篇的同学&#xff0c;请戳蓝色字体阅读。作者方程老师&#xff0c;是前某跨国通信公司高级测试经理&#xff0c;目前为某互联网名企资深测试技…

【算法基础】1.4 高精度(模拟大数运算:整数加减乘除)

文章目录高精度加法题目描述解法高精度减法题目描述解法讲解高精度乘法题目描述解法讲解高精度除法题目描述解法讲解本文主要讲解高精度计算&#xff0c;包括加法、减法、乘法和除法。 对于Python选手&#xff0c;python自带高精度计算&#xff1b;Java也有BigInteger类。但是对…

javaEE 初阶 — 多线程— JUC(java.util.concurrent) 的常见类

文章目录1. Callable 接口1.1 Callable 的用法2. ReentrantLock2.1 ReentrantLock 的缺陷2.1 ReentrantLock 的优势3. 原子类4. 信号量 Semaphore5. CountDownLatch6. 相关面试题1. Callable 接口 类似于 Runnable 一样。 Runnable 用来描述一个任务&#xff0c;描述的任务没有…