多线程案例-线程池

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

线程池的引入

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

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

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

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

解决方案有两种:

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());
                }
            });
        }
    }
}


相关文章
|
1月前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
112 38
|
27天前
|
Java
.如何根据 CPU 核心数设计线程池线程数量
IO 密集型:核心数*2 计算密集型: 核心数+1 为什么加 1?即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保 CPU 的时钟周期不会被浪费。
34 4
|
1月前
|
Java
线程池内部机制:线程的保活与回收策略
【10月更文挑战第24天】 线程池是现代并发编程中管理线程资源的一种高效机制。它不仅能够复用线程,减少创建和销毁线程的开销,还能有效控制并发线程的数量,提高系统资源的利用率。本文将深入探讨线程池中线程的保活和回收机制,帮助你更好地理解和使用线程池。
86 2
|
1月前
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
96 4
|
1月前
|
Prometheus 监控 Cloud Native
在 Java 中,如何使用线程池监控以及动态调整线程池?
【10月更文挑战第22天】线程池的监控和动态调整是一项重要的任务,需要我们结合具体的应用场景和需求,选择合适的方法和策略,以确保线程池始终处于最优状态,提高系统的性能和稳定性。
254 2
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
27 3
|
2月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
23 2
|
2月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
38 2
|
2月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
24 1
|
1月前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
62 0