javaEE初阶 — 线程池

news2024/12/23 20:00:50

文章目录

  • 线程池
    • 1 什么是线程池
    • 2 标准库中的线程池
      • 2.1 什么是工厂模式
      • 2.2 如何使用标准库中的线程池完成任务
      • 2.3 ThreadPoolExecutor 构造方法的解释
    • 3 实现一个线程池

线程池

1 什么是线程池


随着并发程度的提高,随着对性能要求标准的提高会发现,好像线程创建也没有那么的轻量。

当需要频繁的创建和销毁线程的时候,就会发现开销好像还挺大的。

解决办法:

  • “轻量级线程”,也就是 协程纤程
  • 使用 线程池,来降低创建和销毁线程的开销。

线程池就像是一个存储多个线程的容器,事先把需要使用的线程创建好并放到池中。
后续使用的时候,直接从池子里拿即可,用完了就再还给池子。

创建和销毁线程,是交由操作系统内核完成的
从池子里获取和还给池子,是用户代码就可以实现的。

所以,从池里获取和还给池,要比创建和销毁更加的高效。

2 标准库中的线程池


在 java 标准库中,也是提供了现成的的线程池,可以直接使用。

ExecutorService pool = Executors.newFixedThreadPool(10);


上述代码的作用是 创建一个线程池,池子里的线程数目固定为10个

此处的 new (newFixedThreadPool)是方法名字的一部分,而不是 new 关键字。
这个操作使用某个类中的某个静态方法,直接构造出一个对象来。
(相当于是把 new 操作给隐藏到这样的方法背后了)

像这样的方法就称为 “工厂方法”,提供这个工厂方法的类就叫做 “工厂类”

此处的代码使用的是 “工厂模式”,这种设计模式。

2.1 什么是工厂模式


工厂模式可以用一句话表示,使用普通的方法来代替构造方法创建对象

如果构造方法只是构造一种对象,那还比较好办;但如果要构造多种不同情况的对象就不好办了。

举个例子:

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

    public Point(double r, double a) {}
}


很明显这里的代码有问题,正常来说,多个构造方法是通过 “重载” 的方式来提供的。

重载的要求是,方法名相同,参数的个数或者类型不同。

上述的代码,编译器会因为这是一个方法写了两次而报错。

为了解决上面的问题就可使用工厂模式。

class PointFactory {
    public static Point makePointByXY(double x, double y) {}

    public static Point makePointByRA(double r, double a) {}
}

Point p = PointFactory.makePointByXY(10, 20);


普通方法,方法名字没有限制。因此有很多方式构造,就可以直接使用不同的方法名即可。
此时方法的参数是否要区分,已经不重要了。

2.2 如何使用标准库中的线程池完成任务


线程池提供了一个重要的方法,submit 可以给线程池提交若干个任务。

package thread;

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

// 使用标准库的线程池
public class ThreadDemo6 {

    public static void main(String[] args) {
        // 创建一个有十个线程的线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            int num = i;
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("酒國最牛!!!" + num);
                }
            });
        }
    }
}




此处要注意的是,当前操作是往线程池里放 1000 个任务。
这 1000 个任务就是这 10 个线程来平均分配的,差不多就是一个线程执行 100 个。

但是这里的平均又不是严格意义上的平均,可能有的多一个,有的少一个。
(每个线程都执行完一个任务之后,再立即执行下一个任务。由于每个任务执行的时间都差不多,因此每个线程做的任务数量就差不多)

进一步的可以认为,这 1000 个任务,就再一个队列中排队。
这 10 个线程,就一次来去队列中的任务,取一个就执行一个,执行完之后再执行下一个。


标准库中有哪几种不同的线程池

newCachedThreadPool() 这个线程池里的线程数量是动态变化的。
如果任务多了,就多搞几个线程,如果任务少了,就少搞几个线程。

newSingleThreadExecutor() 这个线程池里的线程只有一个。

newFixedThreadPool 这个线程池上面代码介绍了。

newScheduledThreadPool() 这个线程池类似于定时器,也是让任务延时执行。
只不过执行的时候不是有扫描线程自己执行了,而是由单独的线程池来执行。

2.3 ThreadPoolExecutor 构造方法的解释


上述这些线程池,本质都是通过包装 ThreadPoolExecutor 来实现出来的
ThreadPoolExecutor 这个线程池使用起来更麻烦一点,所以才提供了工厂类,让其的使用变得简单。
这里指的麻烦的意思是功能更强大

打卡 java 文档 ,ThreadPoolExecutor 这个线程池就在下面这个包里。



找到上面的包并点击它,下滑找到下面的圈出的内容。



1、corePoolSize 这个参数是核心线程数,maximumPoolSize 这个参数是最大核心线程数。

2、ThreadPoolExecutor 线程池相当于把里面的线程分为两类:

  • 一类是正式员工(核心线程
  • 一类是临时工/实习生

这两者加在一起就组成了最大线程数。

如果任务比较多,显然需要更多线程,此时多搞一些线程,成本也是值得的。
但是一个程序有时候任务多,有时候任务少,如果此时的任务比较少,线程还是那么多,
就非常不合适了。此时就需要对现有的线程进行一定的淘汰。

整体的策略是。正式员工保底,临时工动态调节。


不同的程序特点不同,需要设置的线程数也是不同的。
这里考虑两个极端情况:

  • CPU 密集型
    每个线程执行的任务都是狂转CPU(进行一系列的算数运算),此时线程池线程数,最多也不该超过CPU核数。
    如果搞的线程特别多了,也没有足够的空间使用。

  • IO 密集型
    每个线程干的工作就是等待 IO(读写硬盘、读写网卡、等待用户输入…),不吃 CPU。
    此时这样的线程处于阻塞状态,不参与CPU调度。这个时候多搞一些线程都无所谓,不再受制于CPU核数了。

    然而实际开发中并没有程序符合这两种理想模型,真实的程序往往一部分是CPU密集型,一部分是IO密集型。
    具体这个程序多少工作量是 吃CPU,多少工作量是等待IO,这是不确定的。

我们也只能在实践中确定线程的数量,也就是通过测试和实验的方式。

3、long keepAliveTime 这个参数描述了临时工可以摸鱼的最大时间,也就是临时使用的线程。

4、TimeUnit unit 这个参数描述的是时间单位(s、ms、分钟)

5、BlockingQueue workQueue 这个参数描述了这是线程池的任务队列。
每个工作线程都是在不停的尝试 take 的,如果有任务,就 take 成功;如果没有就阻塞。

6、ThreadFactory threadFactory 这个参数是用于创建线程的,线程池是需要创建线程的。

7、RejectedExecutionHandler handler 这个参数描述了线程池的 “拒接策略”
也是一个特殊的对象,描述了当线程池任务队列满了,如果继续添加任务会有什么样的行为。

以下是几种拒绝策略:

  • 第一种策略是,如果任务太多了,队列满了,就直接抛出异常
  • 第二种策略是,如果队列满了,多出来的任务是谁添加的,谁就负责执行
  • 第三种策略是,如果队列满了,丢弃最早的任务
  • 第四种策略是,丢弃最新的任务

3 实现一个线程池


一个线程池,至少要有两大部分。

  1. 阻塞队列 - 保存任务
  2. 若干个工作线程

1、创建若干个线程

 public MyThreadPool(int n) {
     // 创建线程
     for (int i = 0; i < n; i++) {
         Thread thread = new Thread(() -> {
             while (true) {
                 try {
                     Runnable runnable = queue.take(); //拿到任务
                     runnable.run();//执行任务
                 }catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         });
         thread.start(); //启动线程
     }
 }


2、给线程池里的线程注册任务

public void submit(Runnable runnable) {
    try {
        queue.put(runnable);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}


3、测试结果

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("酒國:" + n);
        }
    });
}

完整代码

package thread;

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

class  MyThreadPool {
    // 此处不涉及时间,只有任务,使用 Runnable 即可
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();

    // n 表示线程的数量
    public MyThreadPool(int n) {
        // 创建线程
        for (int i = 0; i < n; i++) {
            Thread thread = new Thread(() -> {
                while (true) {
                    try {
                        Runnable runnable = queue.take(); //拿到任务
                        runnable.run();//执行任务
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start(); //启动线程
        }
    }

    // 注册任务给线程池
    public void submit(Runnable runnable) {
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadDemo7 {

    public static void main(String[] args) {
        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("酒國:" + n);
                }
            });
        }
    }
}


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

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

相关文章

[cpp进阶]C++异常

文章目录C语言传统处理错误的方式C异常概念C异常使用异常的抛出和捕获异常的重新抛出异常安全异常规范自定义异常体系C标准库的异常体系异常的优缺点C语言传统处理错误的方式 传统的错误处理机制&#xff1a; 终止程序。assert断言直接终止程序。缺点&#xff1a;过于粗暴&am…

Fiddler抓取手机APP报文

Http协议代理工具有很多&#xff0c;比如Burp Suite、Charles、Jmeter、Fiddler等&#xff0c;它们都可以用来抓取APP报文&#xff0c;其中charles和Burp Suite是收费的&#xff0c;Jmeter主要用来做接口测试&#xff0c;而Fiddler提供了免费版&#xff0c;本文记录一下在Windo…

位运算做加法,桶排序找消失元素,名次与真假表示,杨氏矩阵,字符串左旋(外加两道智力题)

Tips 1. 2. 3. 大小端字节序存储这种顺序只有在放进去暂时存储的时候是这样的&#xff0c;但是一旦我里面的数据需要参与什么运算之类的&#xff0c;会“拿出来”先恢复到原先的位置再参与运算&#xff0c;因此&#xff0c;大小端字节序存储的什么顺序不影响移位运算等等…

【案例教程】CLUE模型构建方法、模型验证及土地利用变化情景预测实践技术

【前沿】&#xff1a;土地利用/土地覆盖数据是生态、环境和气象等领域众多模型的重要输入参数之一。基于遥感影像解译&#xff0c;可获取历史或当前任何一个区域的土地利用/土地覆盖数据&#xff0c;用于评估区域的生态环境变化、评价重大生态工程建设成效等。借助CLUE模型&…

声音产生感知简记

声音产生 人的发音器官包括:肺、气管、声带、喉、咽、鼻腔、口腔、唇。肺部产生的气流冲击声带,产生震动。 声带每开启和闭合一次的时间是基音周期(Pitch period,T),其到数为基音频率(F.=1/T,基频),范围在70-450Hz。基频越高,声音越尖细,如小孩的声音比大人尖,就是…

编译错误2

本文迁移自本人网易博客&#xff0c;写于2015年11月25日&#xff0c;编译错误2 - lysygyy的日志 - 网易博客 (163.com)1、error C2059:语法错误&#xff1a;“<L_TYPE_RAW>”error C2238:意外的标记位于“;”之前.错误代码定位于&#xff1a;BOOL TreeView_GetCheckState…

excel函数公式:常用高频公式应用总结 上篇

公式1&#xff1a;条件计数条件计数在Excel的应用中十分常见&#xff0c;例如统计人员名单中的女性人数&#xff0c;就是条件计数的典型代表。条件计数需要用到COUNTIF函数&#xff0c;函数结构为COUNTIF(统计区域,条件)&#xff0c;在本例第一个公式COUNTIF(B:B,G2)中&#xf…

《栈~~队列~~优先级队列》

目录 前言&#xff1a; 1.stack 1.stack的介绍 2.stack的使用&#xff1a; 3.stack的模拟实现 4.有关stack的oj笔试题 2.queue 1.队列的介绍 2.队列的使用 3.队列的模拟实现 4.有关队列的oj笔试题 3.priority_queue 1.优先级队列的介绍 2.优先级队列的使用 3.优先级队列的模拟实…

挥别2022,坦迎2023。

第一章&#xff1a;CSDN&#xff0c;我来啦&#xff01;第一节&#xff1a;初遇&#xff01;2022-08-13&#xff0c;我和CSDN相遇啦&#xff01;CSDN&#xff0c;你好呀&#xff01;2022年8月13日&#xff0c;是我与你相遇的日子。这是一个值得纪念的时刻。从此之后&#xff0c…

English Learning - L1-10 时态(下) 2023.1.5 周四

English Learning - L1-10 时态&#xff08;下&#xff09; 2023.1.5 周四8 时态8.3 完成时态核心思想&#xff1a;回首往事&#xff08;一&#xff09;现在完成时核心思想用法延续动作延续时间 “动作一直持续了。。。”延续动&#xff08;无延续时间&#xff09; “做过。。…

AtCoder Beginner Contest 284 A - E

题目地址&#xff1a;AtCoder Beginner Contest 284 - AtCoder 一个不知名大学生&#xff0c;江湖人称菜狗 original author: jacky Li Email : 3435673055qq.com Time of completion&#xff1a;2023.1.8 Last edited: 2023.1.8 目录 题目地址&#xff1a;AtCoder Beginner C…

基于FPGA的UDP 通信(一)

引言手头的FPGA开发板上有一个千兆网口&#xff0c;最近准备做一下以太网通信的内容。本文先介绍基本的理论知识。FPGA芯片型号&#xff1a;xc7a35tfgg484-2网口芯片&#xff08;PHY&#xff09;&#xff1a;RTL8211网络接口&#xff1a;RJ45简述以太网什么以太网&#xff1f;以…

k8s之实战小栗子

写在前面 本文一起看一个基于k8s的实战小栗子&#xff0c;在这篇文章 中我们基于docker搭建了一个WordPress网站。本文就通过k8s再来实现一遍。架构图如下&#xff1a; ![在这里插入图片描述](https://img-blog.csdnimg.cn/9c73ac0c183a429a8f4b1a2feb363527.png 从上图可以…

使用Origin计算数据的上升\下降时间

使用Origin计算上升/下降时间计算上升时间1导入数据&#xff0c;做图2、选择合适的数据范围3、选择上升时间和上升范围两个测量参数&#xff0c;获得结果4、更改区间&#xff0c;并导出数据计算下降时间1、将感兴趣区域移动到下降沿2、更改为测量下降沿参数获得结果上升时间小工…

【Kotlin】空安全 ④ ( 手动空安全管理 | 空合并操作符 ?: | 空合并操作符与 let 函数结合使用 )

文章目录一、空合并操作符 ?:二、空合并操作符与 let 函数结合使用一、空合并操作符 ?: 空合并操作符 ?: 用法 : 表达式 A ?: 表达式 B如果 表达式 A 的值 不为 null , 则 整个表达式的值 就是 表达式 A 的值 ; 如果 表达式 A 的值 为 null , 则 整个表达式的值 就是 表达…

vue-路由的使用方式

1.下载路由 使用npm的下载: # vue2对应版本 npm i vue-router3# vue3对应版本 npm i vue-router42.路由初试化 路由的三种模式: history, 指定路由的模式, 有hash,history,memory三种模式,一般使用第一种和第三种模式 createWebHashHistory hash模式 > http://localhost…

十大字符串函数与内存操作函数

前言&#xff1a;我们知道在C语言的库中有许许多多的库函数&#xff0c;今天我就来介绍一下自己对两大类库函数中一些常用函数的认识和理解&#xff0c;希望对大家有帮助。 说明&#xff1a;下文中会花较大篇幅实现这些库函数的模拟&#xff0c;请大家不要觉得库函数直接用就好…

UNet入门总结

作者&#xff1a;AI浩 来源&#xff1a;投稿 编辑&#xff1a;学姐 Unet已经是非常老的分割模型了&#xff0c;是2015年《U-Net: Convolutional Networks for Biomedical Image Segmentation》提出的模型。 论文连接&#xff1a;https://arxiv.org/abs/1505.04597 在Unet之前…

Android 深入系统完全讲解(4)

4 SystemServer 创建过程 SystemServer 进程非常关键了&#xff0c;我们上层的服务都是在这里以线程的形式存在&#xff0c;比如 AMS&#xff0c;PMS&#xff0c;WindowManagerService&#xff0c;壁纸服务&#xff0c;而关于调试这个服务进程&#xff0c;我们随后就会讲到。 …

虚拟人-面部表情-Audio2Face语音驱动表情

任务&#xff1a; 输入自己的音频&#xff0c;导入maya模型&#xff0c;让maya模型通过音频驱动说话 教程&#xff1a; https://www.bilibili.com/video/BV1rZ4y1R7H4/?p2&spm_id_frompageDriver&vd_sourceef114f70c3fd4d5394f12dbd3d022bbe 一.下载和安装 1.首先…