当前位置: 首页 > article >正文

多线程---线程池

1.线程池

线程池可以简单理解为一段可以存放多个线程的空间,同时线程池也是一种多线程处理形式,它是一种管理和复用线程的机制。

随着互联网的发展,我们对性能的要求进一步的提高了,而线程频繁创建和销毁的开销对于今天的我们就有点不可接受了。所以大佬们就发明了线程池,线程池最大的好处就是可以让我们更加高效的创建和销毁线程。

也就是说,我们可以提前把线程创建好,放在线程池中,需要用到线程的时候,我们随时到线程里面去取,用完再放回线程池里面。

为什么说直接创建线程会比从线程池中取出线程的开销会更大呢?

这就涉及到操作系统了,一个操作系统等于一个内核+配套的应用程序。一个操作系统只有一个内核,而一个内核要给所有的应用程序提供服务支持的。

对于程序员来说,如果有一段代码实在应用程序中完成的,那么折断代码就是可控的。但是如果有一段代码需要进入内核里面,由内核完成一系列操作,由于程序员写的代码是无法干预内核的,则我们称这段代码是不可控的。

从操作系统的层面去创建新的线程(直接创建线程),就需要操作系统内核配合完成,这是一个不可控的操作,当我们直接从线程池中取出线程,这个操作靠纯应用程序代码就可以完成,这是一个可控的操作。

则使用线程池,就可以省下应用程序切换到内核中运行的开销。而且我们通常认为可控的过程比不可控的过程更高效。

2.ThreadPoolExecutor类介绍

 Java标准库中也为我们提供了一个可以直接使用的线程池类---ThreadPoolExecutor类。

ThreadPoolExecutor的核心方法是submit(Runnable)方法,通过Runnable描述一段要执行的任务,通过submit方法将任务放到线程池中,此时线程池里的线程就会执行这样的任务。

 3.介绍构造ThreadPoolExecutor的参数

这是一个高频面试题,需要重视。

下图是ThreadPoolExecutor的一些构造方法

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

3.1 int corePoolSize 和 int maximumPoolSize

 corePoolSize表示线程池中的核心线程数,也表示该线程池中至少有多少个参数。线程池一创建,这些核心线程数也会随之创建,直到整个线程池被销毁,这些线程才会销毁。

maximumPoolSize表示线程池中的最大线程数(核心线程数+非核心线程数),非核心线程具有一个自适应的特点,如果程序要执行的任务不繁忙,非核心线程就会被回收,如果程序要执行的任务很繁忙,非核心线程就会再创建。 

Java的线程池中里面包含几个线程,是可以动态调整的。

任务多的时候,会自动扩容更多的线程,任务少的时候,会把额外的非核心线程干掉,节省资源

 3.2  long keepAliveTime 和 TimeUnit unit

keepAliveTime表示允许飞鹤心线程空闲的最大时间,unit则表示keepAliveTime的指定时间单位,uint是一个枚举类型,它提供了不同时间单位之间的转换方法。

当非核心线程处于空闲的时间超过keepAliveTime指定的时间后,并且此时线程数量大于核心线程数量时,超出核心线程的这些非核心线程就会被回收。

 3.3 BlockingQueue<Runnable> workQueue

workQueue表示一个工作队列,该队列是用来存储线程池要执行的任务。

线程池本质上也是一个生产者消费者模型,调用submit就是再生产任务,线程池里面的线程就是在消费任务。 

3.4  ThreadFactory threadFactory

threadFactory是一个工厂类,可以认为它是一个创建线程的工厂,参与具体的创建线程工作,通过不同线程工厂创建的线成对于一些属性进行了不同的初始化设置。

工厂模式

工厂模式是一种创建对象的设计模式,它提供了一种创建对象的方式,将对象的创建和使用分离。 

工厂模式也是一种设计模式,与单例模式是并列关系,它是用来弥补构造方法的缺陷的。 

举例:点的表示方法有两种方式,分别为点坐标和极坐标,但是我们如何可以根据我们的需求随时切换这两种点的表示方式呢?

想法一:构造方法

 但是由于构造方法的名字是固定的,要想提供不同的版本,就需要通过重载,但是有时候重载不一定能成功。

如下图

此时,我们通过工厂方法,就可以很好的解决该问题。

工厂方法的核心,通过静态方法,把构造对象new的过程和根据各种属性初始化的过程给封装起来了,提供多组静态方法,实现不同情况的构造。 

并且我们将提供工厂方法的类称为工厂类 。

class Point{

    public static Point makePointByXY(double x,double y){
        Point point=new Point();
        //通过x和y给point进行属性设置
        return point;
    }

    public static Point makePointByRA(double r,double a){
        Point point=new Point();
        //通过r和a对point进行属性设置
        return point;
    }

}

3.5 RejectedExecutionHandler handler 

该参数的意思是拒绝策略,该参数用于任务量超出了线程池的负荷量了,接下来怎么处理。 

线程池中的submit方法是把任务添加到任务队列中,而任务队列是阻塞队列,队列满了,在添加任务就会阻塞,一般不希望程序阻塞太多。

对于线程池来说,发现入队列操作时,发现队列满了,不会真的触发“入队列操作“,不会真阻塞,而是会执行拒绝策略相关的代码。 

线程池中有4中拒绝策略

拒绝策略 

1.AbortPolicy():超出符合,线程池直接抛出异常

2.CallerRunsPolicy(): 让调用submit方法的线程自行执行任务

3.DiscardOldestPolicy():丢弃队列中最老的任务

4.DiscardPolicy():丢弃最新的任务,也就是丢弃submit的的任务

4. Executors

由于ThreadPoolExecutor的构造太过复杂,Java标准库中也提供了另一组类Executors对ThreadPoolExecutor进行了进一步的封装,简化线程池的使用。

 Executors创建线程池的几种方式:

1.newFixedThreadPool:创建固定线程数的现场池

2.newCacheThreadPool:创建线程数目动态增长的线程池

3.newSingleThreadxecutor:创建只包含单个线程的线程池

4.newScheduleThreadPool:设定 延迟时间后执行命令,或者定期执行命令的线程池

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

public class Demo4 {
    public static void main(String[] args) {
        ExecutorService threadPool=  Executors.newFixedThreadPool(4);
        //向线程池中添加100个任务
        for (int i = 0; i < 100; i++) {
            threadPool.submit(()->{
                System.out.println("hello"+" "+Thread.currentThread().getName());
            });
        }
    }
}

5.模拟实现一个线程池

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.lang.Runnable;
class MyThreadPool{
    //使用阻塞队列作为任务队列
    BlockingQueue<Runnable> blockingQueue=null;

    public MyThreadPool(int n){//固定线程数
        blockingQueue=new LinkedBlockingQueue<>(1000);
        //创建线程来执行任务
        for (int i = 0; i < n; i++) {
            Thread t=new Thread(()->{
                while (true){
                    try {
                        Runnable runnable= blockingQueue.take();
                        runnable.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }
    }
    //将任务存在任务队列
    public void submit(Runnable runnable) throws InterruptedException {
        blockingQueue.put(runnable);
    }

}
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool myThreadPool=new MyThreadPool(4);
        for (int i = 0; i < 100; i++) {
            myThreadPool.submit(()->{
                System.out.println("hello"+Thread.currentThread());
            });
        }

    }
}

http://www.kler.cn/a/387490.html

相关文章:

  • 日志系统实践
  • UML系列之Rational Rose笔记一:用例图
  • Go语言之路————func
  • 后端技术选型 sa-token校验学习 下 结合项目学习 后端鉴权
  • GO语言实现KMP算法
  • 介绍PyTorch张量
  • # filezilla连接 虚拟机ubuntu系统出错“尝试连接 ECONNREFUSED - 连接被服务器拒绝, 失败,无法连接服务器”解决方案
  • SQL,力扣题目1127, 用户购买平台
  • 本地 Hadoop 开发环境搭建详解
  • MTK6833/MT6833(天玑700)安卓核心板_联发科5G智能通讯模块安卓主板定制
  • 如何在下载我上传的数据时自动设置 Content-Type
  • Mac brew安装软件镜像加速
  • Android的Handler
  • 手写Golang泛型栈和队列的库函数
  • 万字长文解读深度学习——卷积神经网络CNN
  • JS | JS中获得鼠标位置的属性有哪些?
  • 【ShuQiHere】️`adb kill-server` 和 `adb start-server` 命令的作用
  • 如何自定义一个函数有strlen的功能(不创建新的临时变量)(c基础)
  • 机器学习系列----岭回归(Ridge Regression)简介及实现
  • 【复平面】-复数相乘的几何性质
  • 从0开始深度学习(28)——序列模型
  • 在 CIFAR10 数据集上训练 Vision Transformer (ViT)
  • 解释一下Java中的异常处理机制
  • IDM扩展添加到Edge浏览器
  • 怎么给llama3.2-vision:90b模型进行量化剪枝蒸馏
  • 类加载的生命周期?