Java之线程池

news2024/11/29 8:49:08

目录

一.上节复习

1.阻塞队列

二.线程池

1.什么是线程池

2.为什么要使用线程池

3.JDK中的线程池

三.工厂模式

1.工厂模式的目的

四.使用线程池

1.submit()方法

2.模拟两个阶段任务的执行

五.自定义一个线程池

 六.JDK提供线程池的详解

1.如何自定义一个线程池?

 2.创建线程池的构造方法的参数及含义

3.描述线程池的工作原理

4.拒绝策略

5.为什么不推荐使用系统自带的线程池

七.自定义线程池

1.自定义线程池

 2.拒绝策略测试


一.上节复习

上节内容指路:Java之阻塞队列和消息队列

1.阻塞队列

基于阻塞队列和生产者消费者模型可以实现消息队列

wait()和notify()使用的时机

虚假唤醒:把wait()的判断条件放在一个while循环里面,唤醒之后需要重新检查等待条件

二.线程池

1.什么是线程池

JDBC编程中,通过DataSource获取Connection就用到了池(数据库连接池)的概念                          数据库连接池中有一些已经建立了连接(输入完账号密码的状态)的connection

 当Java程序需要数据库连接时,只需要从数据库连接池中获取到一个空闲的连接进行使用

当Java程序使用完连接之后,就会将当前连接返还给数据库连接池

线程池里放的是线程本身,当程序启动时就创建若干个线程,如果有任务就处理,没有任务就阻塞等待.

2.为什么要使用线程池

为了减少系统创建线程的开销

如果我们不使用线程池,每一次使用创建线程,销毁线程(用户态到内核态),很浪费系统的资源,而且再多线程的环境下,我们一次使用可能是多个线程,多个一起创建很浪费系统的资源,大大减少了系统执行的效率.使用线程池,每一次到池中拿到一个线程,只需要涉及到用户态的操作.

3.JDK中的线程池

1. 用来处理大量短时间工作任务的线程池,如果池中没有可用的线程将创建新的线程,如果线程空闲60秒将收回并移出缓存
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

2. 创建一个操作无界队列且初始线程数为n的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

无界队列: 指的是对队列中的元素个数不加限制,可能出现内存被消耗殆尽的情况

3. 创建一个操作无界队列且只有一个工作线程的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

4. 创建一个单线程执行器,可以在给定时间后执行或定期执行。
 ScheduledExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();

5. 创建一个指定大小的线程池,可以在给定时间后执行或定期执行。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);

6. 创建一个指定大小(不传入参数,为当前机器CPU核心数)的线程池,并行地处理任务不保证处理顺序
ExecutorService executorService = Executors.newWorkStealingPool();

三.工厂模式

1.工厂模式的目的

解决构造方法创建对象的缺陷

以下情况会发生问题,(id,name)和(age,name)会出现相同的参数列表不能够重载,因此不能同时书写这两个构造方法

 我们可以通过两个静态方法来进行创建

class Student {
    private int id;
    private int age;
    private String name;

    public static Student createByIdAndName(int id, String name) {
        Student student = new Student();
        student.setId(id);
        student.setName(name);
        return student;
    }

    public static Student createByAgeAndName(int age, String name) {
        Student student = new Student();
        student.setAge(age);
        student.setName(name);
        return student;
    }


    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

四.使用线程池

1.submit()方法

通过submit方法可以看出我们可以提交任务到线程池,线程池中提供线程进行执行任务.

2.模拟两个阶段任务的执行

public class Demo03_ThreadPoolUse {
    public static void main(String[] args) throws InterruptedException {
        //创建一个初始大小为3的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; ++i) {
            int taskId = i;
            threadPool.submit(() -> {
                System.out.println("我是任务:" + taskId+","+Thread.currentThread().getName());

            });
        }

        //模拟线程等待一段时间
        TimeUnit.SECONDS.sleep(5);
        System.out.println("第二阶段开始");
        for (int i = 10; i < 20; ++i) {
            int taskId = i;
            threadPool.submit(() -> {
                System.out.println("我是任务:" + taskId+","+Thread.currentThread().getName());

            });
        }


    }
}

打印的内容:

注意:此时程序并没有结束,在等待其他任务提交到线程池进行执行

五.自定义一个线程池

1.可以提交任务到线程池,那么就需要一种数据结构来保存任务----可以考虑使用阻塞队列
2.创建线程池需要指定初始的线程数量,这些线程不断的扫描阻塞队列,如果有任务就立即执行.

可以考虑使用线程池对象的构造方法,接收要创建线程的数据并在构造方法中完成线程的创建

public class MyThreadPool {
    //定义一个阻塞队列来保存要执行的任务
    BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(3);

    //提供一个方法,用来提交任务
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }

    //构造方法完成线程的初始化
    public MyThreadPool(int capacity) {
        if (capacity <= 0) {
            throw new IllegalArgumentException("capacity is illegal");
        }
        //完成线程的创建,扫描队列,取出任务并执行
        for (int i = 0; i < capacity; i++) {
            Thread thread = new Thread(() -> {
                while (true) {
                    //取出任务
                    try {
                        Runnable take = queue.take();
                        take.run();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }

            });
            thread.start();
        }
    }
}

测试自定义的线程池:

public class Demo04_MyThreadPool {
    public static void main(String[] args) throws InterruptedException {
        //创建一个初始大小为3的线程池
        MyThreadPool threadPool = new MyThreadPool(3);
        for (int i = 0; i < 10; ++i) {
            int taskId = i;
            threadPool.submit(() -> {
                System.out.println("我是任务:" + taskId + "," + Thread.currentThread().getName());

            });
        }

        //模拟线程等待一段时间
        TimeUnit.SECONDS.sleep(2);
        System.out.println("第二阶段开始");
        for (int i = 10; i < 20; ++i) {
            int taskId = i;
            threadPool.submit(() -> {
                System.out.println("我是任务:" + taskId + "," + Thread.currentThread().getName());

            });
        }
    }
}

打印结果:

 六.JDK提供线程池的详解

1.如何自定义一个线程池?

通过工厂方法获得的线程池最终都是创建一个ThreadPoolExecutor对象

 2.创建线程池的构造方法的参数及含义

  1. int corePoolSize  核心线程数,创建线程是包含的最小线程数
  2. int maximumPoolSize  最大线程数,也叫临时线程数(核心线程数不够时,允许创建最大的线程数)
  3. long keepAliveTime  临时空闲时长,超过这个时间自动释放
  4. TimeUnit unit   空闲的时间单位,和keepAliveTime一起使用
  5. BlockingQueue<Runnable> workQueue 用来保存任务的阻塞队列
  6. ThreadFactory threadFactory  线程工厂,如何去创建线程
  7. RejectedExecutionHandler handler  拒绝策略,触发的时机,当线程池处理不了过多的任务

3.描述线程池的工作原理

即上面七个参数如何搭配使用

先来举一个现实中的例子帮助我们理解工作原理:饭店老板根据人流量安排桌子数(线程数)

1.饭店里面初始有五张桌子(核心线程数为5)

2.下午3点,人数少于5,5张桌子足够接待顾客(核心线程数大于任务数)

3.到了晚饭点,人数大于5,不够接待顾客,顾客需要拿号排队等待(向阻塞队列加入任务)

4.排号人数过多,老板在饭店门口临时加了10张桌子(创建10个临时线程)

5.在用餐高峰期,门口10张桌子全部用完,排号也特别多,就让新来的顾客下次再来(执行拒绝策略)

6.当人数减少,门口10张桌子空了下来,过了半小时还没人来,将10张桌子回收(临时线程达到空闲时常,回收临时线程)

  1. 当任务添加到线程池中时,先判断任务数是否大于核心线程数,如果不大于,直接执行任务
  2. 任务数大于核心线程数,则加入阻塞队列
  3. 当阻塞队列满了之后,会创建临时线程,会按照最大线程数,一次性创建到最大线程数
  4. 当阻塞队列满了并且临时线程也创建完成,再提交任务,就会执行拒绝策略.
  5. 当任务减少,临时线程达到空闲时长时,会被回收.

4.拒绝策略

AbortPolicy:直接拒绝任务的加入,并且抛出RejectedExecutionException异常

CallerRunsPolicy:返回给提交任务的线程执行

DiscardOldestPolicy:舍弃最老的任务

DiscardPolicy:舍弃最新的任务

根据自己的业务场景选择合适的拒绝策略

5.为什么不推荐使用系统自带的线程池

1.由于使用了无界队列,可能会有内存耗尽的风险

默认是长度最大的阻塞队列


2.临时线程数无法控,也可能会存在资源耗尽

七.自定义线程池

1.自定义线程池

public class Demo_05_Create {
    public static void main(String[] args) throws InterruptedException {
        //创建线程池并指定参数
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 1, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10), new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 10; ++i) {
            int taskId = i;
            threadPool.submit(() -> {
                System.out.println("我是任务:" + taskId + "," + Thread.currentThread().getName());

            });
        }

        //模拟线程等待一段时间
        TimeUnit.SECONDS.sleep(5);
        System.out.println("第二阶段开始");
        for (int i = 10; i < 20; ++i) {
            int taskId = i;
            threadPool.submit(() -> {
                System.out.println("我是任务:" + taskId + "," + Thread.currentThread().getName());

            });
        }


    }
}

 2.拒绝策略测试

1.AbortPolicy

将阻塞队列的大小改为1,然后在执行方法

2.CallerRunsPolicy

返回给调用者main方法线程

3.DiscardOldestPolicy

抛弃了一部分的任务,老的

4.DiscardPolicy

抛弃了一部分的任务,新的

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

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

相关文章

【计网】第三章 数据链路层(3)信道划分介质访问控制

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 3.5-1 信道划分介质访问控制&#xff08;播报信道中应用&#xff09;一、传输数据使用的两种链路二、介质访问控制 三、信道划分 介质访问控制&#xff08;静态划分…

协程切换原理与实践 -- 从ucontext api到x86_64汇编

目录 1.协程切换原理理解 2.ucontext实现协程切换 2.1 实现流程 2.2 根据ucontext流程看协程实现 2.3 回答开头提出的问题 3.x86_64汇编实现协程切换 3.1libco x86_64汇编代码分析 3.2.保存程序返回代码地址流程 3.3.恢复程序地址以及上下文 4.实现简单协程框架 1.协程…

《编程思维与实践》1071.猜猜猜

《编程思维与实践》1071.猜猜猜 题目 思路 对于首字符而言,如果后一位字符与之相同,则首位选法只有1种,不同则2种; 对于最后一位字符而言,如果前一位字符与之相同,则末位选法只有1种,不同则2种; 对于中间的字符而言,有以下几种可能: 1.中间字符与前后字符均不同且前后字符不同…

企业挑选人力资源管理系统,需要从哪些角度考察?

企业在挑选人力资源管理系统时&#xff0c;除了要考虑到企业自身的主要需求外&#xff0c;还应该从哪些角度考察人力资源管理系统呢&#xff1f;一起来看看吧~ 一. 数据是否共通 企业在人力资源管理系统时通常有多个功能模块的需求。除了要看系统是否具备这些功能模块&#xff…

一分钟图情论文:《数据与信息之间逻辑关系的探讨——兼及DIKW概念链模式》

一分钟图情论文&#xff1a;《数据与信息之间逻辑关系的探讨——兼及DIKW概念链模式》 1989年&#xff0c;Ackoff R L在论文&#xff1a;《From data to wisdom》中正式提出DIKW概念链模型&#xff0c;在该模型提出后的20年间&#xff0c;在计算机学科、信息管理学科、图书情报…

数据结构--线段树

写在前面&#xff1a; 学习之前需要知道以下内容&#xff1a; 1. 递归 2. 二叉树 文章目录 线段树介绍用途建树修改单点修改区间修改 查询 代码实现。建树更新lazy传递查询 练习洛谷 P3372 【模板】线段树 1题目描述题解 线段树 介绍 线段树是一种二叉树&#xff0c;也可以…

【5G RRC】5G中的服务小区和邻区测量方法

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

STL配接器(容器适配器)—— stack 的介绍使用以及模拟实现。

注意 &#xff1a; 以下所有文档都来源此网站 &#xff1a; http://cplusplus.com/ 一、stack 的介绍和使用 stack 文档的介绍&#xff1a;https://cplusplus.com/reference/stack/stack/ 1. stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&…

Matlab进阶绘图第20期—带类别标签的三维柱状图

带类别标签的三维柱状图是一种特殊的三维柱状图。 与三维柱状图相比&#xff0c;带类别标签的三维柱状图通过颜色表示每根柱子的所属类别&#xff0c;从而可以更加直观地表示四维/四变量数据。 由于Matlab中未收录带类别标签的三维柱状图的绘制函数&#xff0c;因此需要大家自…

Java 使用 jdbc 连接 mysql

简介 Java JDBC 是 Java Database Connectivity 的缩写&#xff0c;它是一种用于连接和操作数据库的标准 API。Java JDBC 可以让 Java 程序通过 JDBC 驱动程序连接到各种不同类型的数据库&#xff0c;并且执行 SQL 语句来实现数据的读取、插入、更新、删除等操作。在本篇文章中…

Springboot整合Flowable流程引擎

文章目录 前言1. Flowable的主要表结构1.1 通用数据表&#xff08;通用表&#xff09;1.2运行时数据表&#xff08;runtime表&#xff09;1.3.历史数据表&#xff08;history表&#xff09;1.4. 身份数据表&#xff08;identity表&#xff09;1.5. 流程定义数据表&#xff08;r…

C++: 并行加速图像读取和处理的过程

文章目录 1. 目的2. 设计3. 串行实现4. 并行实现5. 比对&#xff1a;耗时和正确性6. 加速比探讨 1. 目的 读取单张图像&#xff0c;计算整图均值&#xff0c;这很好实现&#xff0c;运行耗时很短。 读取4000张相同大小的图像&#xff0c;分别计算均值&#xff0c;这也很好实现…

【OpenCv • c++】形态学技术操作 —— 开运算与闭运算

&#x1f680; 个人简介&#xff1a;CSDN「博客新星」TOP 10 &#xff0c; C/C 领域新星创作者&#x1f49f; 作 者&#xff1a;锡兰_CC ❣️&#x1f4dd; 专 栏&#xff1a;【OpenCV • c】计算机视觉&#x1f308; 若有帮助&#xff0c;还请关注➕点赞➕收藏&#xff…

openGauss5.0.0在vscode成功调试

之前在虚拟机上编译成功过&#xff0c;但今天启动数据库的时候出现权限错误问题&#xff0c;我重新删除了data文件夹&#xff0c;重新初始化启动数据库还是不成功&#xff0c;后来对报错文件进行赋权&#xff0c;成功解决&#xff01; 问题&#xff08;一&#xff09; 1.启动…

图像水印MATLAB实验

文章目录 一、实验目的二、实验内容1. 简单的可见水印嵌入实验2. 不可见脆弱水印实验3. 不可见鲁棒水印实验 一、实验目的 了解数字图像水印技术的基本原理、分类和应用。掌握简单的可见水印和不可见水印的嵌入方法。实现一种基于DCT的不可见鲁棒水印&#xff0c;并进行水印鲁…

Dubbo 服务端源码深入分析 (7)

目录 1. 前提 2. 认识 Protocol 和 ProxyFactory Protocal ProxyFactory Dubbo服务流程 服务端源码分析 测试代码&#xff1a; Protocal代理的源码 ProxyFactory源码&#xff1a; 获取invoker对象 具体步骤 1. 我们调用的是ProxyFactory的代理对象的getInvoker方法…

Linux线程同步(6)——更高并行性的读写锁

互斥锁或自旋锁要么是加锁状态、要么是不加锁状态&#xff0c;而且一次只有一个线程可以对其加锁。读写锁有 3 种状态&#xff1a;读模式下的加锁状态&#xff08;以下简称读加锁状态&#xff09;、写模式下的加锁状态&#xff08;以下简称写加锁状态&#xff09;和不加锁状态&…

django视图(request请求response返回值)

一、视图函数介绍 视图就是应用中views.py中定义的函数&#xff0c;称为视图函数 def index(request):return HttpResponse("hello world&#xff01;") 1、视图的第一个参数必须为HttpRequest对象&#xff0c;还可能包含下参数如通过正则表达式组获取的位置参数、通…

VBA——01篇(入门篇——简单基础语法)

VBA——01篇&#xff08;入门篇——简单基础语法&#xff09; 1. 语法格式1.1 简单语法1.2 简单例子 2. 变量2.1 常用数据类型2.2 声明变量的常用方式2.3 简单例子 3. 单元格赋值3.1 直接赋值3.2 拷贝单元格 4. 简单的逻辑语法4.1 简单if4.2 简单for循环4.2.1 简单语法例子4.2.…

基于混合整数二阶锥(MISOCP)的配电网重构(附matlab代码)

参考资料&#xff1a;主动配电网网络分析与运行调控 (sciencereading.cn) 配电网重构是指在满足配电网运行基本约束的前提下&#xff0c;通过改变配电网中一个或多个开关的状态对配电网中一个或多个指标进行优化。通过配电网重构&#xff0c;可以在不增加设备投资的情况下&…