Java多线程案例——线程池及ThreadPoolExecutor类

news2025/1/15 13:44:32

一,线程池

1.为什么会有线程池?线程池和多线程的区别?

为了很好的解决高并发问题,提高计算机的运行效率,提出了多线程来取代多进程(因为一个线程的创创建、销毁和调度比进程更加“轻量”,所以线程也被称作“轻量级进程”),这就是线程存在的意义;

随着并发程度的提高,随着我们对于性能要求标准的提高,我们发现线程的创建也没有那么“轻量”,因为线程的创建,销毁和调度都源自于操作系统内核,频繁的对线程进行操作开销也会很大,所以线程池的概念也随之诞生。

线程池就是在多线程的基础上,减少了对线程频繁创建、销毁和调度的操作,来降低创建、销毁线程的开销,其核心思想:事先把需要使用的线程创建好放到“池”中,后面需要使用的时候,直接从池里获取,用完了还给“池”。

主要区别:

多线程:创建、销毁线程都是交由操作系统内核来完成

线程池:事先创建好线程,从“池子”里获取还给“池子”,都是由用户代码实现,不用交给内核操作

主要改进:

减少了在多线程环境下操作系统内核频繁创建、销毁线程的开销

2.线程池的创建

在Java标准库中,对于线程池的创建主要是通过Executors这个工厂类(工厂模式就是使用普通方法来代替构造方法创建对象=相当于是把new操作给隐藏到普通方法后面)来实现。

构造方法(只介绍常见的):

newFixedThreadPool

创建指定数目线程的线程池

newCachedThreadPool

线程数量是动态变化的,任务多了就多创建几个线程

newSingleThreadExecutor

线程池内只有一个线程

newScheduledThreadPool

类似于定时器,只是执行扫描任务的时候不是由扫描线程完成,而是线程池内的线程完成

submit方法:

submit方法是为了用来给线程池提交任务,传入的参数是Runnable类型的。

线程池代码示例:

/**
 * 创建一个包含10个线程的线程池去执行1000个任务
 * 每个任务被执行的时候打印哪个线程正在执行任务来知道当前被调度的线程
 */
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadDemo3 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "正在执行任务");
                }
            });
        }
    }
}

发现此时线程执行任务的顺序是随机的,因为每个线程执行完一个任务之后再立即取下一个任务,由于每个线程执行任务的时间不同,因此每个线程并不是按照一定的顺序来执行。而且我们发信这里的进程并没有结束,这点类似于之前说的Timer计时器类,所创建的线程都是前台线程会阻止进程的结束。

二,模拟实现线程池

简单的线程池需要实现两个功能:

  1. 阻塞队列,保存任务

  1. 若干个工作线程

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

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) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }
    }

    //注册任务给线程池
    public void submit(Runnable runnable) {
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadDemo6 {
    public static void main(String[] args) {
        MyThreadPool pool = new MyThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            int n = i;
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() +"正在执行任务");
                }
            });
        }
    }
}

通过编译可以执行效果同Java标注库提供的submit方法一样。

三,ThreadPoolExecutor

上面构造方法中提到的那些线程池本质上都是通过包装ThreadPoolExecutor类来实现的,只是ThreadPoolExecutor这个线程池用起来更麻烦(所以Java标准库给我们提供了工厂类),因为其参数过多使其使用起来变得复杂,但是在面试中ThreadPoolExecutor类的参数的含义考的非常多,所以我们需要做一个简单的了解。

ThreadPoolExecutor的构造方法:

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecytionHandler handler)

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecytionHandler handler)

这里我们只讲解第四种构造方法,因为第四种的构造方法最为复杂,包含了所有参数。

corePoolSize(核心线程数):

一般指最少包含的线程数量(类比公司的正式员工);

maximumPoolSize(最大线程数):

线程池所能容纳的最大线程数,当活跃线程数达到该数值后,后续的新任务将会阻塞(类比公司的实习生,正式员工+实习生 = 最大线程数);

keepAliveTime(线程闲置超时时长):

如果超过该时长,非核心线程就会被回收(描述了实习生可以偷懒的最大时间);

unit(时间单位):

指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分);

BlockingQueue<Runnable> workQueue(线程池的任务队列):

任务队列通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中,采用阻塞队列实现;

ThreadFactory threadFactory(线程工厂):

用于指定为线程池创建新线程的方式;

RejectedExecytionHandler handler(拒绝策略):

描述线程的“拒绝策略”,也是一个特殊的对象,描述了当线程朝任务队列满了,如果继续添加任务会有啥样的行为。

拒绝策略:

ThreadPoolExecutor.AbortPolicy

如果队列满了,就直接抛出一个异常

ThreadPoolExecutor.CallerRunsPolicy

如果队列满了,多出来的任务,谁加的谁负责

ThreadPoolExecutor.DiscardOldestPolicy

如果队列满了,丢弃最早的任务

ThreadPoolExecutor.DiscardPolicy

如果队列满了,丢弃最新的任务

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

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

相关文章

杰卡德相似度(Jaccard)详解及在UserCF中的应用

1、杰卡德相似度(Jaccard) 这个是衡量两个集合的相似度一种指标。 两个集合A和B的交集元素在A&#xff0c;B的并集中所占的比例&#xff0c;称为两个集合的杰卡德相似系数&#xff0c;用符号J(A,B)表示 另一种表示的方法&#xff1a; jaccard系数衡量维度相似性 jaccard系数很…

IT运维.服务器常见资质认证

3C证书 强制要求 CCC认证的全称为“中国强制性产品认证“ 它是为保护消费者人身安全和国家安全、加强产品质量管理、依照法律法规实施的一-种产品合格评定制度。, 节能

spring之动态代理

文章目录前言一、JDK动态代理1、业务接口OrderService2、目标对象OrderServiceImpl3、客户端程序Client4、InvocationHandler 的实现类TimeInvocationHandler5、运行结果二、CGLIB动态代理1、先引入依赖2、目标类 UserService3、客户端程序Client4、MethodInterceptor的实现类T…

温振传感器的信号输出方式及应用领域

在振动测量系统中&#xff0c;测量振动的仪器排在前端。温振传感器也称为温度振动传感器&#xff08;变送器&#xff09;&#xff0c;它可以将被测对象的振动量&#xff08;位移、速度&#xff09;准确接受后&#xff0c;并将此机械量转换为电信号显示出来。 在工业生产、食品…

内存对齐(memory align)

0. 内存结构 我们平时所称的内存也叫随机访问存储器&#xff08;random-access memory&#xff09;也叫RAM。而RAM分为两类&#xff1a; 一类是静态RAM&#xff08;SRAM&#xff09;&#xff0c;这类SRAM用于前边介绍的CPU高速缓存L1Cache&#xff0c;L2Cache&#xff0c;L3C…

不求星光灿烂,但愿岁月静好

作者&#xff1a;非妃是公主 专栏&#xff1a;《程序人生》 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录不求星光灿烂&#xff0c;但愿岁月静好说一说这一年的自己的收获吧2022年的追求自我学会拒绝尝试表达…

Unreal单播委托

单播委托只能注册一个函数:无参无返回值给委托绑定函数:判断如果委托有绑定函数就发起广播:解绑:绑定方式除了BindUObject,还有BindUFunction,通过这种方式绑定需要给函数添加UFUNCTION标记:还有BindLambda匿名函数:BindRaw可以绑定原生C类中的函数:无参有返回值定义委托类型:声…

Linux进程状态与系统负载检测

1.基础知识-进程的5个状态进程可以分为五个状态&#xff0c;分别是&#xff1a;1&#xff09;创建状态一个应用程序从系统上启动&#xff0c;首先就是进入创建状态&#xff0c;需要获取系统资源创建进程管理块&#xff08;PCB&#xff09;完成资源分配。2) 就绪状态在创建状态完…

Dextran-Azide,Dextran-N3结构式;叠氮修饰的葡聚糖 科研用试剂说明

Dextran-N3,叠氮基团葡聚糖 英文名称&#xff1a;Dextran-Azide,Dextran-N3 中文名&#xff1a;叠氮修饰的葡聚糖 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 外观: 固体或类白色絮状&#xff0c;取决于分子量 溶剂&#xff1a;溶于大部分有机溶剂&…

kafka单节点部署,手把手从零到一

kafka单节点部署 书接上回&#xff1a;zookeeper单节点部署&#xff0c;手把手从零到一 建议配套观看 2、kafka的单节点部署 2.1、下载 这里如果和zookeeper相似的就不再赘述&#xff0c;直接上命令 wget https://archive.apache.org/dist/kafka/2.8.2/kafka_2.12-2.8.2.tgz…

深入了解ArrayBlockingQueue 阻塞队列

1. 前言 开始正式了解阻塞队列之前&#xff0c;我们需要了解什么是队列。 队列有什么作用。其实队列的作用就是解耦&#xff0c;更加确切的说应该是生产者以及消费者 之间的解耦 今天就让我们来看下ArrayBlockingQueue 的实现。虽然通过名称就可以看到&#xff0c;无非是通过数…

Theory for the information-based decomposition of stock price

文章目录MotivationThe potential of Brogaard DecompositionIntuitions for Brogaard decompositionTechnique details in Brogaard decompositionDefine the VAR systemIdentify the VAR systemVariance decompositionSummaryMain ReferencesMotivation Brogaard et al. (20…

1000字带您了解网络设备的接口分类和接口编号规则

通过本文&#xff0c;您可以了解到设备的接口分类和接口编号规则。 文章目录一、接口分类1.1 物理接口1.1.1 管理接口1.1.1 业务接口LAN侧接口WAN侧接口1.2 逻辑接口二、接口编号规则2.1 物理接口编号规则三、总结一、接口分类 接口是设备与网络中的其它设备交换数据并相互作用…

3.3 行列式的几何意义

文章目录二维面积三维体积多维体积行列式是线性代数一个非常重要的内容&#xff0c;也是非常难的领域.行列式在欧几里得空间里还有特殊的几何意义。二维面积 &esmp; 两个向量围成的平行四边形的面积就是这两个向量组成的矩阵的行列式的绝对值。以两个向量(3.−2)T(3.-2)^…

结构体 · 内存对齐

欢迎来到 Claffic 的博客 &#x1f49e;&#x1f49e;&#x1f49e; 前言&#xff1a; 在初识C语言中简单介绍了结构体&#xff0c;结构体可以理解为不同类型数据的集合体&#xff0c;但是你想过结构体的大小是如何计算的吗&#xff1f;看完这篇博客&#xff0c;你就能给自己答…

Linux 计算机网络 route 路由表、多网段与 bond 的故事

Linux 计算机网络 route 路由表、多网段与 bond 的故事 序 在之前的章节中&#xff0c;介绍了计算机网络的发展以及各种解析&#xff0c;在之中我们提到了每个主机设备都会维护一张自己的路由表&#xff0c;通过路由表来确定在不同网络之间&#xff0c;怎么将数据规划传输到各…

1988-2020年31省基尼系数数据

1、时间&#xff1a;1988-2020年 2、范围&#xff1a;31省 3、指标&#xff1a;包括省基尼系数年度数据&#xff0c;省城市和农村基尼系数年度 4、来源及计算方法说明附在文件内 5、指标说明&#xff1a; 基尼系数&#xff08;英文&#xff1a;Gini index、Gini Coefficie…

LeetCode 94. 二叉树的中序遍历

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 94. 二叉树的中序遍历&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 LeetC…

Mybatis获取参数

Mybatis获取参数 配置模板 mybatis获取参数值的两种方式 1、&{}&#xff1a; 字符串拼接 2、#{}&#xff1a; 占位符赋值 MyBatis获取参数值的各种情况&#xff1a; MyBatis获取参数值的各种情况&#xff1a; 1、mapper接口方法的参数为单个的字面量类型 可以通过&#xf…

双系统下 linux挂载window磁盘

如果想让linux访问window分区磁盘&#xff0c;呈只读状态&#xff0c;解决办法是bios取消window快速开机。永久挂载windows磁盘 https://blog.csdn.net/yuehenmiss/article/details/124737456 # 创建挂载目录 sudo mkdir /window # 挂载分区 sudo mount /dev/sda1 /window # 查…