我的编程空间,编程开发者的网络收藏夹
学习永远不晚

多线程案例(4)-线程池

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

多线程案例(4)-线程池

大家好,我是晓星航。今天为大家带来的是 多线程案例-线程池 相关的讲解!😀

多线程案例四

四、线程池

线程池是什么

虽然创建线程 / 销毁线程 的开销

想象这么一个场景:

在学校附近新开了一家快递店,老板很精明,想到一个与众不同的办法来经营。店里没有雇人, 而是每次有业务来了,就现场找一名同学过来把快递送了,然后解雇同学。这个类比我们平时来一个任务,起一个线程进行处理的模式。

很快老板发现问题来了,每次招聘 + 解雇同学的成本还是非常高的。老板还是很善于变通的,知道了为什么大家都要雇人了,所以指定了一个指标,公司业务人员会扩张到 3 个人,但还是随着 业务逐步雇人。于是再有业务来了,老板就看,如果现在公司还没 3 个人,就雇一个人去送快 递,否则只是把业务放到一个本本上,等着 3 个快递人员空闲的时候去处理。这个就是我们要带出的线程池的模式。

🧨线程池最大的好处就是减少每次启动、销毁线程的损耗。🧨

标准库中的线程池

  • 使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
  • 返回值类型为 ExecutorService
  • 通过 ExecutorService.submit 可以注册一个任务到线程池中.
ExecutorService pool = Executors.newFixedThreadPool(10);pool.submit(new Runnable() {    @Override    public void run() {        System.out.println("hello");   }});

上述代码第一行代码就是创建了一个线程池,池子里线程数目固定是10个。

这个操作,使用某个类的某个静态方法,直接构造出了一个对象来(相当于是把 new 操作,给隐藏到这样的方法后面了)

像这样的方法,就称为"工厂方法"

提供工厂方法的类,也就成为"工厂类"

此处这个代码就使用了"工厂模式"这种 设计模式

那么什么是工厂模式呢?

答:工厂模式,一句话表示,使用普通的方法来代替构造方法,创建对象。为啥要代替呢,因为构造方法有坑,他只能构造一种对象,如果要构造多种不同情况的对象,就难搞了。

例如我们需要使用笛卡尔坐标系来构造坐标,空间直角坐标系和极坐标系,那么此时我们的代码就会是:

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) {            }}public class ThreadDemo26 {    public static void main(String[] args) {        Point P = PointFactory.makePointByXY(10,20);    }}

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

这里我们区分一下重载和重写的区别:

重载(overload):指一个类中可以有多个方法具有相同的名字,但这些方法的参数不同(参数的类型和个数不同)。分别在父类子类里也是可能构成重载的。

重写(override):在不同类中(指父类和子类)中,两个具有相同方法名和相同参数的方法,称作重写。 (如果是其他语言,重写方法不一定通过父类子类)

重写本质上就是用一个新的方法,代替旧的… 就得要求新的方法和旧的方法,名字/参数都得一摸一样

下面我们为大家带来几个线程池的使用举例:

运行程序之后发现,main线程结束了,但是整个进程没有结束。线程池中的线程都是 前台线程。此时会阻止进程结束。(前面定时器 Timer 也是同理)

下属代码为什么不能直接写 System.*out*.println("hello" + n); 呢?

答:因为变量捕获,这里的 i 是主线程里的局部变量(在主线程的栈上),随着主线程这里的代码块执行结束就销毁了。很可能主线程这里的 for 执行完了,当前 run 的任务在线程池里还没排到呢,此时 i 就销毁了

变量捕获(必须是不可修改的变量才能捕获 有final修饰的变量也可捕获):在定义 run 的时候,偷偷把 i 当前的值记住。后续执行run的时候,就创建一个也叫做 i 的局部变量,并且把这个值赋值过去

final:

final修饰类,表示该类是无法被任何其他类继承的,意味着此类在一个继承树中是一个叶子类,并且此类的设计已被认为很完美而不需要进行修改或扩展。

final修饰类中的方法,表示该类是无法被任何其他类继承的,不可以被重写;也就是把该方法锁定了,以防止继承类对其进行更改。

final修饰类中的变量,表示该变量一旦被初始化便不可改变。

此处要注意,当前是往线程池里放了1000个任务,1000个任务就是让这 10 个线程来平均分配一下,差不多一个线程执行100个任务,但是注意这里并非是严格的平均,可能有的多一个有的少一个,都很正常。(每个线程都执行完一个任务之后,再立即取下一个任务,由于每个任务执行时间都差不多,因此每个线程做的任务数量就差不多)

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

因为线程调度是随机分配的,因此会出现顺序不一样的情况。

Executors 创建线程池的几种方式

  • newFixedThreadPool: 创建固定线程数(可能是多个)的线程池
  • newCachedThreadPool: 创建线程数目动态增长的线程池.
  • newSingleThreadExecutor: 创建只包含单个线程的线程池.
  • newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer. 执行的时候不是由扫描线程自己执行,而是由单独的线程池来执行。

总结:实践中确定的线程数量,也很简单,通过测试/实验的方式

Executors 本质上是 ThreadPoolExecutor 类的封装.

ThreadPoolExecutor 提供了更多的可选参数, 可以进一步细化线程池行为的设定. (后面再介绍)

实现线程池

  • 核心操作为 submit, 将任务加入线程池中 使用 Worker 类描述一个工作线程.
  • 使用 Runnable 描述一个任务.
  • 使用一个 BlockingQueue 组织所有的任务
  • 每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行.
  • 指定一下线程池中的最大线程数 maxWorkerCount; 当当前线程数超过这个最大值时, 就不再新增 线程了.
class Worker extends Thread {    private LinkedBlockingQueue<Runnable> queue = null;    public Worker(LinkedBlockingQueue<Runnable> queue) {        super("worker");        this.queue = queue;   }    @Override    public void run() {        // try 必须放在 while 外头, 或者 while 里头应该影响不大        try {            while (!Thread.interrupted()) {                Runnable runnable = queue.take();                runnable.run();           }       } catch (InterruptedException e) {       }   }}
public class MyThreadPool {    private int maxWorkerCount = 10;    private LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue();    public void submit(Runnable command) {        if (workerList.size() < maxWorkerCount) {            // 当前 worker 数不足, 就继续创建 worker            Worker worker = new Worker(queue);            worker.start();       }        // 将任务添加到任务队列中        queue.put(command);   }    public static void main(String[] args) throws InterruptedException {        MyThreadPool myThreadPool = new MyThreadPool();        myThreadPool.execute(new Runnable() {            @Override            public void run() {                System.out.println("吃饭");           }       });  Thread.sleep(1000);   }}

自己实现线程池版本二:

import java.util.concurrent.BlockingQueue;import java.util.concurrent.LinkedBlockingQueue;class MyThreadPoll {    //此处不涉及 "时间",此处只有任务,就直接使用 Runnable 即可~~    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();    //n 表示线程的数量    public MyThreadPoll(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 ThreadDemo27 {    public static void main(String[] args) {        MyThreadPoll pool = new MyThreadPoll(10);        for (int i = 0; i < 1000; i++) {            int n = i;            pool.submit(new Runnable() {                @Override                public void run() {                    System.out.println("hello" + n);                }            });        }    }}

四个拒绝策略:

例如我们此时在学习,而有好朋友叫我们去打游戏

那么对应上面情况:

  1. 要学习的东西太多,我直接开摆,啥也不干了…
  2. 学习任务太多了,让朋友们自己去玩。(此时朋友们自己去打游戏了)
  3. 直接不学了,开玩!
  4. 拒绝去玩,继续学习。(至于朋友们有没有玩我们不必管)

感谢各位读者的阅读,本文章有任何错误都可以在评论区发表你们的意见,我会对文章进行改正的。如果本文章对你有帮助请动一动你们敏捷的小手点一点赞,你的每一次鼓励都是作者创作的动力哦!😘

来源地址:https://blog.csdn.net/xinhang10/article/details/132129808

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

多线程案例(4)-线程池

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

多线程案例(4)-线程池

文章目录 多线程案例四四、线程池 大家好,我是晓星航。今天为大家带来的是 多线程案例-线程池 相关的讲解!😀 多线程案例四 四、线程池 线程池是什么 虽然创建线程 / 销毁线程 的开销 想象这么一个场景:
2023-08-19

多线程编程(3):线程池ThreadPo

在面向对象编程中,经常会面对创建对象和销毁对象的情况,如果不正确处理的话,在短时间内创建大量对象然后执行简单处理之后又要销毁这些刚刚建立的对象,这是一个非常消耗性能的低效行为,所以很多面向对象语言中在内部使用对象池来处理这种情况,以提高性能
2023-01-31

Python中线程池模块之多线程的示例分析

这篇文章将为大家详细讲解有关Python中线程池模块之多线程的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。1、线程池模块引入from concurrent.futures import Thre
2023-06-15

C++ 多线程编程中线程池的应用

c++++ 多线程编程中使用线程池的好处包括:1)减少线程创建次数;2)负载均衡;3)避免资源争用。例如,通过使用线程池将图像转换任务分配给线程池,可以提高文件转换应用程序的转换速度。C++ 多线程编程中线程池的应用在现代 C++ 应用程
C++ 多线程编程中线程池的应用
2024-05-14

Java多线程之线程池七个参数的示例分析

这篇文章主要介绍Java多线程之线程池七个参数的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!ThreadPoolExecutor是JDK中的线程池实现,这个类实现了一个线程池需要的各个方法,它提供了任务提交
2023-06-14

Android的线程、多线程和线程池面试题有哪些

这篇“Android的线程、多线程和线程池面试题有哪些”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Android的线程、多
2023-06-04

java怎么使用线程池启动多线程

在 Java 中,可以使用线程池来启动多线程。以下是使用线程池启动多线程的示例代码:首先,需要导入 `java.util.concurrent.ExecutorService` 和 `java.util.concurrent.Executo
2023-09-15

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录