多线程案例-线程池

简介: 多线程案例-线程池

线程池的引入

最开始,进程能够解决并发变成的问题.

但是由于频繁创建销毁进程,成本比较高.引入了轻量级进程->线程

如果创建/销毁线程的频率进一步提高,此时线程的创建与销毁开销也就不能忽视了.

所以就需要想办法优化此处的线程的创建销毁效率.

解决方案有两种:

1.引入轻量级线程->也称纤程/协程(协程本质,是程序员在用户态代码中进行调度,不是靠内核的调度器调度的~~,节省了很多调度上的开销).纯用户态代码就比在内核内操作更安全更可控.协程是在用户代码中,基于线程封装过来的.本质是程序员在用户态中代码调度,而非内核调度器.

2.线程池,把提前使用的线程准备好.用完了也不要释放而是以备下次使用以节省创建/销毁开销

线程池的最大好处就是减少每次启动,销毁线程的消耗.

标准库中的线程池

使用Executors.newFixedThreadPool(10)能创建出固定包含10个线程的线程池.

返回值为ExecutorService.

通过ExecutorService.submit可以注册一个任务到线程池中.

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

Executor创建线程池的几种方式

newFixedThreadPool:创建固定线程数目的线程池

newCachedThreadPool:创建线程数目动态增长的线程池

newSingleThreadExecutor:创建只包含单个线程的线程池

newScheduledThreadPool:设定延迟时间后执行命令,或者定期执行命令.是进阶版的Timer.

Executor本质上是ThreadPoolExecutor类的封装.

Executors:只是简单用一下.  ThreadPoolExecutor:希望是高度定制化的.

下面来介绍重要的线程池创建方式:ThreadPoolExecutor.

它提供了更多的可选参数,可以进一步细化线程池行为的设定.

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

1.corePoolSize:核心线程数,类似于正式员工的数量.(正式员工,一旦录用,永不辞退).

2.maximumPoolSize:最大线程数.正式员工+临时工项目.(临时工:一段时间不干活,就被辞退)

3.keepAliveTime:临时工允许的空闲时间.(如果空闲时间超过这个时间阈值,就会销毁掉)

4.unit:keepAliveTime的时间单位,是秒,是分还是其它值.

5.workQueue:传递任务的阻塞队列.(和定时器类似,线程池中也持有很多任务,Runnable作为描述任务的主体).

6.threadFactory

创建线程的工厂,参与具体的创建线程工作.通过不同线程工厂创建出的线程相当于对一些属性进行了不同的初始化设置.

线程工厂例子:

class Point {
    public static Point makePointByXY(double x, double y) {
        Point p = new Point();
        p.setX(x);
        p.setY(y);
        return p;
    }
    
    public static Point makePointByRA(double r, double e) {
        Point p = new Point();
        p.setR(r);
        p.setA(a);
        return p;
    }
}

这里的两个方法就称为工厂方法 .

如果把工厂方法放到一个其他的类里面,这个其它的类就叫做"工厂类".

总的来说,通过静态方法封装new操作,在方法内部设定不同的属性完成对象初始化.构造对象的过程就是"工厂模式".

通过这个工厂类,来创建线程对象(Thread对象),在这个类里提供方法,让方法封装new Thread操作,并给Thread一些属性.

7.RejectedExecutionHandler:拒绝策略,如果任务量超出公司的符合了接下来怎么处理.(在阻塞队列中,能够容纳的元素是有上限的)

1.AbortPolicy():超过负荷,直接抛出异常.(新旧任务都不再执行)

2.CallerRunsPolicy():调用者负责处理多出来的任务,即添加任务的线程负责执行.(新任务会执行,只不过不是线程池执行,而是由调用者执行)

3.DiscardOldestPolicy():丢弃队列中最老的任务.(执行新任务,抛弃旧任务)

4.DiscardPolicy():丢弃新来的任务(新的任务就无了,不执行了.调用的线程不会执行,线程池也不会执行).\\

提出问题:创建线程池时,怎么设定线程池的线程数?

由于线程复杂性,很难直接对干线程池的线程池数目进行估算.更合适的方法,通过实验/测试方式找到合适的线程数目.

实现线程池

核心操作为submit,将任务加入线程池中

使用Worker类描述一个工作线程.使用Runnable描述一个任务.

使用一个BlockingQueue组织所有的任务.

每个worker线程要做的事情:不挺从BlockingQueue中取任务并执行.

指定一下线程池中的最大线程数maxWorkerCount;当当前线程数超过这个最大值时,就不再新增线程了.

具体代码及注释如下:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
 
public class MyThreadPoolExecutor {
    //用于保存线程,用于以后能取出线程并修改
    private List<Thread> ThreadList = new ArrayList<>();
    //用于保存任务的队列
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
 
    //通过这个方法,把这个任务添加到线程池中.
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
 
    //通过n指定创建多少个线程
    //创建了一个固定数量的线程池
    public MyThreadPoolExecutor(int n) {
        for(int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
                try {
                    //取出一个任务,并执行
                    Runnable runnable = queue.take();
                    runnable.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t.start();
            ThreadList.add(t);
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        MyThreadPoolExecutor pool = new MyThreadPoolExecutor(4);
        for(int i = 0; i < 1000; i++) {
            int n = i;
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    //要执行的工作
                    System.out.println("执行任务 " + n + ", 当前线程为: " + Thread.currentThread().getId());
                }
            });
        }
    }
}


相关文章
|
29天前
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
3天前
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
32 20
|
9天前
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
|
1月前
|
存储 监控 Java
JAVA线程池有哪些队列? 以及它们的适用场景案例
不同的线程池队列有着各自的特点和适用场景,在实际使用线程池时,需要根据具体的业务需求、系统资源状况以及对任务执行顺序、响应时间等方面的要求,合理选择相应的队列来构建线程池,以实现高效的任务处理。
118 12
|
2月前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
79 1
|
3月前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
135 38
|
3月前
|
Java
.如何根据 CPU 核心数设计线程池线程数量
IO 密集型:核心数*2 计算密集型: 核心数+1 为什么加 1?即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保 CPU 的时钟周期不会被浪费。
152 4
|
3月前
|
Java
线程池内部机制:线程的保活与回收策略
【10月更文挑战第24天】 线程池是现代并发编程中管理线程资源的一种高效机制。它不仅能够复用线程,减少创建和销毁线程的开销,还能有效控制并发线程的数量,提高系统资源的利用率。本文将深入探讨线程池中线程的保活和回收机制,帮助你更好地理解和使用线程池。
164 2
|
3月前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
118 0
|
4月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
79 1