共享模型之工具(一)

news2025/2/26 19:44:42

1.线程池

1.1.线程池产生背景

1>.线程是一种系统资源,每创建一个新的线程都需要占用一定的内存(分配栈内存),在高并发场景下,某一时刻有大量请求访问系统,如果针对每个请求(任务)都创建一个新的线程,那么对内存的占用是相当大的,有可能还出现OOM(内存溢出),甚至会导致整个系统崩溃;

2>.线程的执行需要消耗CPU资源,由于计算机CPU核心数的有限的,如果在系统中创建了大量线程,那么就会有一部分线程在执行过程中无法获取到CPU执行权(/CPU时间片)而处于阻塞状态,从而引起线程上下文切换问题(保存线程的运行状态,下次运行时再恢复到之前的状态).线程上下文的频繁切换对系统性能也有很大的影响,尤其是在高并发环境下,频繁切换线程上下文反而会导致系统性能降低;

针对上述问题可以知道,系统中线程并不是创建的越多越好,而是需要一个容器将数量有限的线程管理起来,对这些线程进行复用(享元模式),以此来减少系统资源的占用.基于此,线程池应运而生;

1.2.线程池概述

线程池是指在初始化一个多线程应用程序过程中提供一个线程集合,在需要执行新的任务时重用这些线程而不是每次都新建一个线程,避免了创建和销毁线程的额外开销,提高响应速度.线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求.然而,增加可用线程数量是可能的.线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到线程池中并等待下一次分配任务;

***注意: 线程池中的线程都是非守护线程,不会随着主线程的结束而结束;

1.3.线程池特点

1.3.1.主要特点

1>.线程复用
2>.控制最大并发数
3>.管理线程

1.3.2. 优势

1>.降低资源消耗.通过重复利用已经创建好的线程降低线程创建和销毁造成的资源消耗;
2>.提高响应速度.当任务到达时,任务可以不需要等待线程创建就可以立即执行;
3>.提高线程的可管理性.线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控;

1.4.JDK提供的线程池

1.4.1.ThreadPoolExecutor对象

在这里插入图片描述

1.4.2.线程池的状态

ThreadPoolExecutor对象使用"int的高3位"来表示线程池状态,"低29位"表示线程数量;
在这里插入图片描述

从数字上比较,TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING!

***注意:RUNNING状态对应的数字"111",最左侧是数字是符号位,"1"表示负("-"),因此它代表的数字是最小的!

说明:

这些信息存储在一个原子变量ctl中,目的是将线程池状态与线程个数合二为一,这样就可以用一次cas原子操作进行赋值;

// c为旧值(期望值),ctlOf返回结果为新值
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))));
// rs为高3位代表线程池状态, wc为低29位代表线程个数,ctl是合并它们
private static int ctlOf(int rs, int wc) { return rs | wc; }

1.4.3.线程池构造方法

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

参数说明:

①,corePoolSize: 核心线程数目(最多保留的线程数)
②.maximumPoolSize: 最大线程数目,它等于corePoolSize+额外创建的线程数
③.keepAliveTime: 生存时间-针对救急线程(额外创建线程)
④.unit: 时间单位-针对救急线程(额外的线程)
⑤.workQueue: 阻塞队列
⑥.threadFactory: 线程工厂-可以为线程创建时起个好名字
⑦.handler: 拒绝策略

根据这个构造方法,JDK Executors类中提供了众多工厂方法来创建各种用途的线程池;

1.4.4.线程池工作方式

***注意: 救急线程有生存时间(执行完任务,一段时间内没有执行其他任务,就会被销毁),而核心线程没有生存时间(会一直运行)!

在这里插入图片描述
在这里插入图片描述
说明:

①.线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务;(线程的创建是懒加载的!)

②.当线程池中的线程数达到corePoolSize并且没有线程空闲(所有的线程都在执行任务),这时再加入任务,新加的任务会被加入workQueue队列排队,直到有空闲的线程;

③.如果队列选择了有界队列,那么任务超过了队列大小时,会创建(maximumPoolSize - corePoolSize)数目的线程来救急;

④.如果线程数到达maximumPoolSize仍然有新任务,这时会执行拒绝策略.拒绝策略jdk提供了4 种实现,其它著名框架也提供了实现:

  • AbortPolicy:让调用者抛出RejectedExecutionException异常,这是默认策略;
  • CallerRunsPolicy:让调用者运行任务(在调用者线程中执行任务);
  • DiscardPolicy:放弃本次任务;
  • DiscardOldestPolicy:放弃队列中最早的任务,本任务取而代之;
    在这里插入图片描述

⑤.当(流量)高峰过去后,超出corePoolSize的救急线程如果一段时间没有任务可做,需要结束它们以节省资源,这个时间由keepAliveTime和unit来控制;

扩展: 其他框架提供的拒绝策略实现

①.Dubbo的实现: 在抛出RejectedExecutionException异常之前会记录日志,并dump 线程栈信息,方便定位问题;

②.Netty的实现: 创建一个新线程来执行任务;

③.ActiveMQ的实现:带超时等待(60s),之后尝试放入队列,类似我们之前自定义的拒绝策略;

④.PinPoint的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略;

1.4.5.newFixedThreadPool: 固定线程数的线程池

1>.底层源码:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

2>.特点:

①.核心线程数 = 最大线程池数,没有救急线程被创建,因此也无需超时时间;
②.阻塞队列是无界的,可以存放任意数量的任务;

3>.使用场景

适用于任务量已知,相对耗时的任务;

1.4.6.newCachedThreadPool: 带缓存功能的线程池

1>.底层源码:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

2>.特点

①.核心线程数是0,最大线程数是Integer.MAX_VALUE,救急线程的空闲生存时间(默认)是60s,意味着:

  • 全部都是救急线程(60s后可以回收);
  • 救急线程可以无限创建(Integer.MAX_VALUE);

②.队列采用了SynchronousQueue实现,特点是它没有容量,没有线程来取是放不进去的(一手交钱、一手交货)

例子:SynchronousQueue队列

@Slf4j
public class TestNewCacheThreadPool {

    public static void main(String[] args) throws InterruptedException {
        SynchronousQueue<Integer> integers = new SynchronousQueue<Integer>();
        new Thread(() -> {
            try {
                log.info("putting{}",1);
                integers.put(1);
                log.info("putted");

                log.info("putting{}",2);
                integers.put(2);
                log.info("putted");
            }catch (Exception ex){
                ex.printStackTrace();
            }
        },"线程t1").start();
        Thread.sleep(1000);

        new Thread(() -> {
            try {
                log.info("taking {}", 1);
                integers.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t2").start();
        Thread.sleep(1);

        new Thread(() -> {
            try {
                log.info("taking {}", 2);
                integers.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t3").start();
    }
}

在这里插入图片描述
3>.使用场景:

整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲分钟后释放线程.适合任务数比较密集,但每个任务执行时间较短的情况;

1.4.7.newSingleThreadExecutor : 单线程的线程池

1>.底层源码:

public static ExecutorService newSingleThreadExecutor() {
        //装饰器模式
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

2>.特点

①.如果是自己创建一个单线程串行执行任务,一旦任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作;

②.Executors.newSingleThreadExecutor()线程个数始终为1,不能修改;

  • FinalizableDelegatedExecutorService应用的是装饰器模式,只对外暴露了ExecutorService接口,因此不能调用ThreadPoolExecutor中特有的方法;

③.Executors.newFixedThreadPool(1)初始时为1,以后还可以修改;

  • 对外暴露的是ThreadPoolExecutor对象,可以强转后调用setCorePoolSize等方法进行修改;

3>.使用场景

希望多个任务排队执行.线程数固定为1,任务数多于1时,会放入无界队列排队.任务执行完毕,这唯一的线程也不会被释放;

1.4.8.newScheduledThreadPool

1>.在"任务调度线程池"功能加入之前,可以使用java.util.Timer来实现定时功能,Timer的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务;

2>.整个线程池表现为:线程数固定,任务数多于线程数时,会放入无界队列排队.任务执行完毕,这些线程也不会被释放.用来执行延迟或反复执行的任务;

3>.例子:

@Slf4j
public class TestScheduledExecutorService {
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

        // 添加两个任务,希望它们都在 1s 后执行
        executor.schedule(() -> {
            log.info("任务1,执行时间:" + new Date());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
        }, 1000, TimeUnit.MILLISECONDS);

        executor.schedule(() -> {
            log.info("任务2,执行时间:" + new Date());
        }, 1000, TimeUnit.MILLISECONDS);
    }
}

在这里插入图片描述
扩展:
1>.scheduleAtFixedRate: 定时执行任务,反复不停的执行

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
log.info("start...");
executor.scheduleAtFixedRate(() -> {
   log.info("running...");
}, 1, 1, TimeUnit.SECONDS);

2>.scheduleAtFixedRate: 延迟不停地执行

ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        log.info("start...");
        pool.scheduleWithFixedDelay(()-> {
            log.info("running...");
            try {
                //延迟时间=sleep时间+设置的间隔时间
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1, 1, TimeUnit.SECONDS);

scheduleWithFixedDelay的间隔是:上一个任务结束<-> 延时 <-> 下一个任务开始,所以间隔都是3s!

1.4.9.提交任务

// 执行任务
void execute(Runnable command);

// 提交任务task,用返回值Future获得任务执行结果
<T> Future<T> submit(Callable<T> task);

// 提交tasks中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
 throws InterruptedException;

// 提交tasks中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
 long timeout, TimeUnit unit) throws InterruptedException;

// 提交tasks中所有任务,但是不会执行所有的任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;

// 提交tasks中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

1.4.10.关闭/停止线程池

1>.shutdown: 优雅的停止

/*
线程池状态变为 SHUTDOWN
- 不会接收新任务
- 但已提交任务会执行完
- 此方法不会阻塞调用线程的执行
*/
void shutdown();

public void shutdown() {
 final ReentrantLock mainLock = this.mainLock;
 mainLock.lock();
 try {
 checkShutdownAccess();
 // 修改线程池状态
 advanceRunState(SHUTDOWN);
 // 仅会打断空闲线程
 interruptIdleWorkers();
 onShutdown(); // 扩展点ScheduledThreadPoolExecutor
 } finally {
 mainLock.unlock();
 }
 // 尝试终结(没有运行的线程可以立刻终结,如果还有运行的线程也不会等)
 tryTerminate();
}

2>.shutdownNow: 强制停止

/*
线程池状态变为 STOP
- 不会接收新任务
- 会将队列中的任务返回
- 并用 interrupt 的方式中断正在执行的任务
*/
List<Runnable> shutdownNow();

public List<Runnable> shutdownNow() { 
 List<Runnable> tasks;
 final ReentrantLock mainLock = this.mainLock;
 mainLock.lock();
 try {
 checkShutdownAccess();
 // 修改线程池状态
 advanceRunState(STOP);
 // 打断所有线程
 interruptWorkers();
 // 获取队列中剩余任务
 tasks = drainQueue();
 } finally {
 mainLock.unlock();
 }
 // 尝试终结
 tryTerminate();
 return tasks;
}

3>.其他方法

// 不在 RUNNING 状态的线程池,此方法就返回 true
boolean isShutdown();
// 线程池状态是否是 TERMINATED
boolean isTerminated();
// 调用shutdown后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池TERMINATED后做些事情,可以利用此方法等待
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;

1.4.11.正确处理执行任务异常

1>.方法1:主动捕捉异常

ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(() -> {
 try {
 log.debug("task1");
 int i = 1 / 0;
 } catch (Exception e) {
 log.error("error:", e);
 }
});

2>.方法2:使用Future收集异常

ExecutorService pool = Executors.newFixedThreadPool(1);
Future<Boolean> f = pool.submit(() -> {
 log.debug("task1");
 int i = 1 / 0;
 return true;
});
log.info("result:{}", f.get());

1.4.12.线程池应用–定时任务

1>.需求

让每周四 18:00:00 定时执行任务

2>.代码实现:

@Slf4j
public class TestScheduleDemo {
    public static void main(String[] args) {
        //当前时间
        LocalDateTime now = LocalDateTime.now();
        //以当前时间为基础找到周四的时间
        LocalDateTime timer = now.withHour(18).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);

        //一周的时间间隔,这也是任务的执行周期/间隔
        long period = 1000 * 60 * 60 * 24 * 7;

        //如果当前时间大于周四,例如今天周五,那么就要到下周四去执行,而不是会退到本周四
        if (now.compareTo(timer) >= 0) {
            timer = timer.plusWeeks(1);
        }

        //任务延迟执行时间=周四时间执行任务的时间-当前时间
        long delay = Duration.between(now, timer).toMillis();

        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        pool.scheduleAtFixedRate(() -> {
            log.info("running");
        }, delay, period, TimeUnit.MILLISECONDS);
    }
}

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

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

相关文章

配置中心Config

引入依赖<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.6.RELEASE</version></parent><properties><spring-cloud.version>Finchley.SR…

设计模式:桥接模式让抽象和实现解耦,各自独立变化

一、问题场景 现在对”不同手机类型“的 “不同品牌”实现操作编程(比如: 开机、关机、上网&#xff0c;打电话等) 二、传统解决方案 传统方案解决手机使用问题类图&#xff1a; 三、传统方案分析 传统方案解决手机操作问题分析 1、扩展性问题(类爆炸)&#xff0c;如果我们…

JavaのString类这一篇就够了(包含StringBuffer_Builder)

1.&#x1f957;String类简介 在我们写代码的时候&#xff0c;String总是充斥着前前后后。 但你会不会经常力不从心&#xff0c; “这个*** 字符串怎么** 转换不成功啊” “*** 这个字符串到底是常量还是对象啊” “这*** 字符串内存结构到底* * * 是什么啊” “为啥我的字符串…

Java 在二叉树中增加一行

623. 在二叉树中增加一行中等给定一个二叉树的根 root 和两个整数 val 和 depth &#xff0c;在给定的深度 depth 处添加一个值为 val 的节点行。注意&#xff0c;根节点 root 位于深度 1 。加法规则如下:给定整数 depth&#xff0c;对于深度为 depth - 1 的每个非空树节点 cur…

LeetCode 83. 删除排序链表中的重复元素

原题链接 难度&#xff1a;easy\color{Green}{easy}easy 题目描述 给定一个已排序的链表的头 headheadhead &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,1,2] 输出&#xff1a;…

Springboot+ElasticSearch构建博客检索系统-学习笔记01

课程简介&#xff1a;从实际需求分析开始&#xff0c;打造个人博客检索系统。内容涵盖&#xff1a;ES安装、ES基本概念和数据类型、Mysql到ES数据同步、SpringBoot操作ES。通过本课&#xff0c;让学员对ES有一个初步认识&#xff0c;理解ES的一些适用场景&#xff0c;以及如何使…

C语言实例|编写C程序在控制台打印余弦曲线

C语言文章更新目录 C语言学习资源汇总&#xff0c;史上最全面总结&#xff0c;没有之一 C/C学习资源&#xff08;百度云盘链接&#xff09; 计算机二级资料&#xff08;过级专用&#xff09; C语言学习路线&#xff08;从入门到实战&#xff09; 编写C语言程序的7个步骤和编程…

30岁了,说几句大实话

是的&#xff0c;我 30 岁了&#xff0c;还是周岁。 就在这上个月末&#xff0c;我度过了自己 30 岁的生日。 都说三十而立&#xff0c;要对自己有一个正确的认识&#xff0c;明确自己以后想做什么&#xff0c;能做什么。 想想时间&#xff0c;过得真快。 过五关斩六将&…

基于圆展开自适应三边测量算法的室内定位

基于圆展开自适应三边测量算法的室内定位 具有无线通信功能的移动设备的日益普及刺激了室内定位服务的增长。室内定位用于实时定位设备位置&#xff0c;方便访问。然而&#xff0c;由于大量障碍物&#xff0c;与室外定位相比&#xff0c;室内定位具有挑战性。全球定位系统非常适…

【MyBatis】| MyBatis分页插件PageHelper

目录 一&#xff1a;MyBatis使⽤PageHelper 1. limit分⻚ 2. PageHelper插件 一&#xff1a;MyBatis使⽤PageHelper 1. limit分⻚ &#xff08;1&#xff09;概念&#xff1a; ①页码&#xff1a;pageNum&#xff08;用户会发送请求&#xff0c;携带页码pageNum给服务器&am…

Pom.xml详解

目录 1、Maven的下载安装 2、什么是pom&#xff1f; 3、较完整的pom元素 4、默认生成Maven工程的pom内容 5、自定义的属性变量 6、依赖管理 6.1、整体依赖关系列表 6.2、依赖关系的传递性 6.3、依赖传递可能造成的问题 6.3.1、scope依赖范围 6.3.2、依赖调节 6.3.3…

【分享】如何通过集简云将ChatGPT人工智能接入到我们的飞书机器人中?

ChatGPT是一款非常强大的人工智能产品&#xff0c;可以有创造性的回复和创作文字&#xff0c;图片&#xff0c;适用于很多办公场景。这篇文章将介绍如何将ChatGPT接入到我们的飞书机器人中。 在集简云中的ChatGPT应用 目前集简云提供了两个ChatGPT应用: OpenAI(ChatGPT&#x…

EdgeCOM嵌入式边缘计算机的参数配置

EdgeCOM嵌入式边缘计算机的参数配置&#xff1a; 下面以 eth0 为例进行命令说明。 在 Linux 系统下&#xff0c;使用 ifconfig 命令可以显示或配置网络设备&#xff0c;使用 ethtool 查询及 设置网卡参数。 设置 IP 地址&#xff0c;查看当前网卡详情&#xff1a; rootfl-imx6u…

数字源表在二极管特性参数分析中的应用

分立器件特性参数测试是对待测器件&#xff08;DUT&#xff09;施加电压或电流&#xff0c;然后测试其对激励做出的响应&#xff0c;通常分立器件特性参数测试需要几台仪器完成&#xff0c;如数字万用表、 电压源、电流源等。然而由数台仪器组成的系统需要分别进行编程、同步、…

ShardingSphere-Proxy5 根据时间分表

0、软件版本 ShardingSphere-Proxy&#xff1a; 5.2.0 MySQL&#xff1a; 8.0.30 系统&#xff1a; win10 1、ShardingSphere-Proxy下载 我们可以在 官网 找到最新版ShardingSphere-Proxy下载&#xff0c;也可以在ShardingSphere仓库中下载 2、ShardingSphere-Proxy配置 …

MySQL存储引擎、事务、索引 | 老杜

目录 一、存储引擎 1、什么是存储引擎 2、怎么设存储引擎 3、常用存储引擎 MyISAM存储引擎 InnoDB存储引擎 MEMORY存储引擎 二、事务 1、什么是事务 2、怎么做到同时成功同时失败 3、怎么提交和回滚呢 4、事务4个特性 A&#xff1a;原子性 C&#xff1a;一致性 …

适用于媒体行业的管理数据解决方案—— StorageGRID Webscale

主要优势 1、降低媒体存储库的复杂性 • 借助真正的全局命名空间在全球范围内存储数据并在本地进行访问。 • 实施纠删编码和远程复制策略。 • 通过单一管理平台管理策略和监控存储。 2、优化媒体工作流 • 确认内容在合适的时间处于合适的位置。 • 支持应用程序直接通过 A…

研报精选230215

目录 【行业230215开源证券】电力设备行业投资策略&#xff1a;特高压建设有望迎来高峰期&#xff0c;解决清洁能源跨区互济瓶颈【行业230215浙商证券】计算机行业【AIGC算力时代系列报告】&#xff1a;ChatGPT研究框架【个股230215国信证券_公牛集团】民用电工行业领军者&…

SpringBoot08:Shiro

什么是Shiro&#xff1f; 一个Java的安全&#xff08;权限&#xff09;框架&#xff0c;可以完成认证、授权、加密、会话管理、Web集成、缓存等 下载地址&#xff1a;Apache Shiro | Simple. Java. Security. 快速启动 先在官网找到入门案例&#xff1a;shiro/samples/quick…

leaflet 鼠标点击弹出popup,显示明星名片(068)

第068个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中显示名人信息,这里给marker绑定popup,同时给每一个icon设定不同的图片。bindPopup(popup) 开始时不显示弹出框。而bindPopup(popup) .openOn(this.map); 开始时候会弹出一个pop 直接复制下面的 vue+leaf…