剖析线程池实现原理
前置推荐阅读:java并发之线程池使用-CSDN博客
自定义实现一个带监控的线程池
首先我们继承ThreadPoolExecutor,实现构造函数以及重写beforeExecute和afterExecute两个函数,具体调用我们会在代码实现层面进行详细的分析。
import java.util.concurrent.*;public class AsyncThreadPool extends ThreadPoolExecutor {/*** 任务队列*/private BlockingQueue<Runnable> workerQueue;public AsyncThreadPool(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler){super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);this.workerQueue = workQueue;}/*** 在任务执行之后**** @param r 执行任务* @param t 异常信息*/@Overrideprotected void afterExecute(Runnable r, Throwable t) {System.out.println("AsyncThreadPool afterExecute threadName:"+Thread.currentThread().getName()+", afterExecutor queueSize:"+workerQueue.size()+" !!!");}/*** 在任务执行之前**** @param t 执行线程* @param r 异常信息*/@Overrideprotected void beforeExecute(Thread t, Runnable r) {System.out.println("AsyncThreadPool beforeExecute threadName:"+Thread.currentThread().getName()+", afterExecutor queueSize:"+workerQueue.size()+" !!!");}}
创建Util并重写ThreadFactory,代码如下:
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;public class AsyncThreadPoolUtil {/*** 默认线程数(当前cpu核心数量)*/private static final int DEFAULT_CORE_THREAD_SIZE = Runtime.getRuntime().availableProcessors() * 2 + 1;/*** 默认工作队列*/private static final LinkedBlockingDeque<Runnable> DEFAULT_WORKER_QUEUE = new LinkedBlockingDeque<>(20);private ThreadPoolExecutor threadPoolExecutor;public AsyncThreadPoolUtil(String threadName){this(DEFAULT_CORE_THREAD_SIZE,DEFAULT_CORE_THREAD_SIZE,DEFAULT_WORKER_QUEUE,threadName);}public AsyncThreadPoolUtil(int coreThreadSize, int maxThreadSize, BlockingQueue<Runnable> workerQueue,String threadName){this(coreThreadSize,maxThreadSize,0L,TimeUnit.SECONDS,workerQueue,threadName);}public AsyncThreadPoolUtil(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,String threadName){this.threadPoolExecutor = new AsyncThreadPool(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,new DefaultThreadFactory(threadName),new ThreadPoolExecutor.CallerRunsPolicy());}/*** The default thread factory*/static class DefaultThreadFactory implements ThreadFactory {private static final AtomicInteger poolNumber = new AtomicInteger(1);private final ThreadGroup group;private final AtomicInteger threadNumber = new AtomicInteger(1);private final String namePrefix;DefaultThreadFactory(String threadName) {SecurityManager s = System.getSecurityManager();group = (s != null) ? s.getThreadGroup() :Thread.currentThread().getThreadGroup();namePrefix = threadName +poolNumber.getAndIncrement() +"-thread-";}@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(),0);if (t.isDaemon()){t.setDaemon(false);}if (t.getPriority() != Thread.NORM_PRIORITY){t.setPriority(Thread.NORM_PRIORITY);}return t;}}/*** 执行runnable 任务* @param runnable 提交任务*/public void execute(Runnable runnable){this.threadPoolExecutor.execute(runnable);}/*** 提交异步任务* @param task 异步任务* @param <T> T* @return Future*/public <T>Future<T> submit(Callable<T> task){return this.threadPoolExecutor.submit(task);}}
编写Test进行验证
public class Test {public static void main(String[] args) {AsyncThreadPoolUtil pool = new AsyncThreadPoolUtil("demo-test-");for (int i=0;i<200;i++){pool.execute(()->{try{TimeUnit.MILLISECONDS.sleep(500);}catch (Exception e){}});}}}
输出信息见截图,由此我们可以在任务执行前以及执行后进行任务的监控,同时可以队列情况。
源码分析
ThreadPoolExecutor类图:
我们从AsyncThreadPool 代码中调用super函数开始看起,该函数中传入:
1.核心线程数:默认情况下不会回收,可通过allowCoreThreadTimeOut函数设置回收,或者设置为0。若无需求,不建议进行核心线程回收。
2.最大线程数:该参数必须大于等于核心线程数,非核心线程数在队列中没有要继续执行任务时会进行回收。
3.非核心线程存活时间
4.非核心线程存活时间单位
5.任务存储队列:当无空闲线城时提交的任务会进入到队列进行等待执行。
6.创建线程工厂:用于创建初始化线程
7.拒绝策略:当无空闲线程且任务队列已满则执行决绝策略。
看完了构造函数创建之后,我们来看任务的提交。在Test中,我们通过pool.execute()函数来提交一个任务到线程池执行,在该函数我们看到线程池中的线程是在提交任务后才进行的初始化。
1. workerCountOf(c)统计当核心线程数量是否已经全部初始化了,如果没有,那么则直接通过addWorker()创建线程执行任务。
2.如果当前核心线程已经全部初始化了,那么则将任务快速添加到队列中,同时校验如果当前线程池已经关闭,那么则移除任务同时执行拒绝策略。如果当前线程池存活线程是0,那么添加工作线程进行任务执行。
3.如果在第2步中添加到任务队列时队列已满,则直接尝试创建非核心线程执行,如果非核心线程也无法创建,那么执行决绝策略。
接下来我们重点分析下 addWorker(Runnable firstTask, boolean core)的函数。
Runnable firstTask
:要执行的第一个任务,如果为null
,则表示新线程将从工作队列中获取任务。boolean core
:指示是否为核心线程,true
表示是核心线程,false
表示非核心线程。
-
循环尝试获取线程池状态(
runStateOf(ctl.get())
):- 如果线程池状态大于或等于
SHUTDOWN
(即线程池正在关闭或已关闭),并且不是在关闭状态下添加新任务到非空队列,那么返回false
,无法添加新工作线程。
- 如果线程池状态大于或等于
-
检查工作线程数量:
- 获取当前线程池的工作线程数量(
workerCountOf(c)
)。 - 如果线程数量已经达到最大容量(
CAPACITY
),或者对于核心线程来说达到了corePoolSize
,对于非核心线程来说达到了maximumPoolSize
,则返回false
,无法添加新工作线程。 - 如果当前线程数量小于上述限制,并且成功通过
compareAndIncrementWorkerCount(c)
方法增加工作线程计数,则跳出循环。
- 获取当前线程池的工作线程数量(
-
创建新工作线程:
- 尝试创建新的
Worker
对象,它是一个继承了Thread
的类,用于执行任务。 - 如果新线程
t
不为空,并且线程池状态允许新线程启动(即runStateOf(ctl.get())
小于SHUTDOWN
或者在关闭状态下且firstTask
为null
),则将新工作线程添加到线程池的workers
集合中,并标记为已添加(workerAdded = true
)。
- 尝试创建新的
-
启动新工作线程:
- 如果工作线程成功添加,调用
t.start()
启动新线程,并将workerStarted
标记为true
。
- 如果工作线程成功添加,调用
-
处理线程启动失败的情况:
- 如果新线程没有成功启动,调用
addWorkerFailed(w)
方法来处理失败情况,这可能包括移除工作线程计数和执行其他清理工作。
- 如果新线程没有成功启动,调用
-
返回结果:
- 返回
workerStarted
,表示新工作线程是否成功启动。
- 返回
/*** Checks if a new worker can be added with respect to current* pool state and the given bound (either core or maximum). If so,* the worker count is adjusted accordingly, and, if possible, a* new worker is created and started, running firstTask as its* first task. This method returns false if the pool is stopped or* eligible to shut down. It also returns false if the thread* factory fails to create a thread when asked. If the thread* creation fails, either due to the thread factory returning* null, or due to an exception (typically OutOfMemoryError in* Thread.start()), we roll back cleanly.** @param firstTask the task the new thread should run first (or* null if none). Workers are created with an initial first task* (in method execute()) to bypass queuing when there are fewer* than corePoolSize threads (in which case we always start one),* or when the queue is full (in which case we must bypass queue).* Initially idle threads are usually created via* prestartCoreThread or to replace other dying workers.** @param core if true use corePoolSize as bound, else* maximumPoolSize. (A boolean indicator is used here rather than a* value to ensure reads of fresh values after checking other pool* state).* @return true if successful*/private boolean addWorker(Runnable firstTask, boolean core) {retry:for (;;) {int c = ctl.get();int rs = runStateOf(c);// Check if queue empty only if necessary.if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;for (;;) {int wc = workerCountOf(c);if (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))return false;if (compareAndIncrementWorkerCount(c))break retry;c = ctl.get(); // Re-read ctlif (runStateOf(c) != rs)continue retry;// else CAS failed due to workerCount change; retry inner loop}}boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {w = new Worker(firstTask);final Thread t = w.thread;if (t != null) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// Recheck while holding lock.// Back out on ThreadFactory failure or if// shut down before lock acquired.int rs = runStateOf(ctl.get());if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {if (t.isAlive()) // precheck that t is startablethrow new IllegalThreadStateException();workers.add(w);int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;}} finally {mainLock.unlock();}if (workerAdded) {t.start();workerStarted = true;}}} finally {if (! workerStarted)addWorkerFailed(w);}return workerStarted;}
我们接着分析创建执行任务Worker(),它继承自AbstractQueuedSynchronizer
并实现了Runnable
接口。Worker
类主要负责维护线程的中断状态和一些次要的记账工作,同时它也实现了任务的运行。
private static final long serialVersionUID
:序列化ID,用于序列化机制。final Thread thread
:当前Worker
线程运行的Thread
对象。如果线程工厂创建线程失败,则为null
。Runnable firstTask
:当前Worker
线程需要执行的第一个任务。如果没有初始任务,则为null
。volatile long completedTasks
:此线程完成的任务数量。
构造函数Worker(Runnable firstTask)
:
- 调用
setState(-1)
初始化锁状态为-1,表示在runWorker
方法执行之前禁止中断。 - 初始化
firstTask
为传入的第一个任务。 - 通过线程工厂创建新线程,并将其赋值给
thread
。
运行方法:
public void run()
:将控制权委托给外部的runWorker
方法,开始工作线程的主运行循环。
锁方法:
Worker
类继承自AbstractQueuedSynchronizer
,提供了锁的获取和释放方法。这些方法用于保护任务执行,防止在等待任务时被中断。
protected boolean isHeldExclusively()
:判断当前线程是否独占锁。protected boolean tryAcquire(int unused)
:尝试获取锁。protected boolean tryRelease(int unused)
:尝试释放锁。public void lock()
:获取锁。public boolean tryLock()
:尝试获取锁,如果锁被占用则立即返回false
。public void unlock()
:释放锁。public boolean isLocked()
:判断锁是否被占用。
中断方法:
void interruptIfStarted()
:如果线程已经开始运行并且尚未中断,则尝试中断该线程。这个方法用于在工作线程等待新任务时,如果线程池正在关闭,则中断工作线程。
Worker
类是ThreadPoolExecutor
线程池中每个工作线程的抽象表示。它负责维护线程的运行状态、锁状态和任务执行状态。通过继承AbstractQueuedSynchronizer
,Worker
类提供了一个简单的互斥锁,以确保在执行任务时不会被中断。此外,Worker
类还提供了中断控制,以确保在适当的时候中断
工作线程,特别是在线程池关闭时。
/*** Class Worker mainly maintains interrupt control state for* threads running tasks, along with other minor bookkeeping.* This class opportunistically extends AbstractQueuedSynchronizer* to simplify acquiring and releasing a lock surrounding each* task execution. This protects against interrupts that are* intended to wake up a worker thread waiting for a task from* instead interrupting a task being run. We implement a simple* non-reentrant mutual exclusion lock rather than use* ReentrantLock because we do not want worker tasks to be able to* reacquire the lock when they invoke pool control methods like* setCorePoolSize. Additionally, to suppress interrupts until* the thread actually starts running tasks, we initialize lock* state to a negative value, and clear it upon start (in* runWorker).*/private final class Workerextends AbstractQueuedSynchronizerimplements Runnable{/*** This class will never be serialized, but we provide a* serialVersionUID to suppress a javac warning.*/private static final long serialVersionUID = 6138294804551838833L;/** Thread this worker is running in. Null if factory fails. */final Thread thread;/** Initial task to run. Possibly null. */Runnable firstTask;/** Per-thread task counter */volatile long completedTasks;/*** Creates with given first task and thread from ThreadFactory.* @param firstTask the first task (null if none)*/Worker(Runnable firstTask) {setState(-1); // inhibit interrupts until runWorkerthis.firstTask = firstTask;this.thread = getThreadFactory().newThread(this);}/** Delegates main run loop to outer runWorker */public void run() {runWorker(this);}// Lock methods//// The value 0 represents the unlocked state.// The value 1 represents the locked state.protected boolean isHeldExclusively() {return getState() != 0;}protected boolean tryAcquire(int unused) {if (compareAndSetState(0, 1)) {setExclusiveOwnerThread(Thread.currentThread());return true;}return false;}protected boolean tryRelease(int unused) {setExclusiveOwnerThread(null);setState(0);return true;}public void lock() { acquire(1); }public boolean tryLock() { return tryAcquire(1); }public void unlock() { release(1); }public boolean isLocked() { return isHeldExclusively(); }void interruptIfStarted() {Thread t;if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {try {t.interrupt();} catch (SecurityException ignore) {}}}}
线程池的工作原理图解:
以下是对本篇文章的的总结:
主要功能和特点:
- 线程池管理:
ThreadPoolExecutor
允许你控制线程的创建和销毁,以及任务的执行和管理。 - 参数化配置:提供了多个参数来调整线程池的行为,包括核心线程数、最大线程数、线程存活时间、工作队列等。
- 任务执行:可以执行任何实现了
Runnable
接口的任务。 - 线程复用:通过重用线程来执行新任务,减少了线程创建和销毁的开销。
- 拒绝策略:当任务太多,无法被线程池及时处理时,可以定义拒绝策略来处理新提交的任务。
关键组件:
- 核心线程数:即使它们是空闲的,也会保持一定数量的线程。
- 最大线程数:线程池中允许的最大线程数量。
- 工作队列:用于存放待执行任务的队列。
- 线程工厂:用于创建新线程。
- 拒绝执行处理器:当任务太多,无法被线程池及时处理时,定义了如何处理新提交的任务。
方法概览:
execute(Runnable command)
:提交一个任务给线程池执行。shutdown()
:平滑地关闭线程池,不再接受新任务,但会处理完已提交的任务。shutdownNow()
:尝试立即停止所有正在执行的任务,并返回等待执行的任务列表。isShutdown()
、isTerminating()
、isTerminated()
:检查线程池的状态。awaitTermination(long timeout, TimeUnit unit)
:等待线程池终止。setCorePoolSize(int corePoolSize)
、setMaximumPoolSize(int maximumPoolSize)
:动态调整线程池的大小。getQueue()
:获取当前的任务队列。
拒绝策略:
AbortPolicy
:默认策略,当任务不能被接受时抛出异常。CallerRunsPolicy
:用调用者线程来运行任务。DiscardPolicy
:直接丢弃任务。DiscardOldestPolicy
:丢弃队列中最旧的任务,并尝试再次提交新任务。
扩展性:
ThreadPoolExecutor
提供了多个钩子方法,如beforeExecute(Thread t, Runnable r)
和afterExecute(Runnable r, Throwable t)
,允许在任务执行前后进行自定义操作。
这个类是Java并发包中的核心组件,为多线程编程提供了强大的工具,使得任务的并发执行更加高效和易于管理。
相关文章:

剖析线程池实现原理
前置推荐阅读:java并发之线程池使用-CSDN博客 自定义实现一个带监控的线程池 首先我们继承ThreadPoolExecutor,实现构造函数以及重写beforeExecute和afterExecute两个函数,具体调用我们会在代码实现层面进行详细的分析。 import java.util.…...

【中危】Oracle TNS Listener SID 可以被猜测
一、漏洞详情 Oracle 打补丁后,复测出一处中危漏洞:Oracle TNS Listener SID 可以被猜测。 可以通过暴力猜测的方法探测出Oracle TNS Listener SID,探测出的SID可以用于进一步探测Oracle 数据库的口令。 建议解决办法: 1. 不应该使…...

三维测量与建模笔记 - 简介
计算机视觉相关主题 主要有两个最主要的层面,几何和语义。几何层面描述了客观事实,比如物体的距离、大小、形状、位置等。语义层面则是从人类抽象出的概念出发,描述了物体是什么、行为是什么、为什么,比如自动驾驶场景中识别出信号…...
Glide 简易教程
文章目录 1 引入依赖2 图片形状2.1 圆形 CircleCrop2.2 旋转 Rotate2.3 圆角 RoundedCorners2.4 自定义圆角 GranularRoundedCorners 1 引入依赖 implementation("com.github.bumptech.glide:glide:4.16.0")2 图片形状 2.1 圆形 CircleCrop Glide.with(this).load…...

flutter 使用三方/自家字体
将字体放入assets/fonts下 在pubspec.yaml文件中flutter下添加如下代码: flutter:fonts:- family: MyCustomFontfonts:- asset: assets/fonts/MyCustomFont.ttf 在flutter Text widget中使用字体 import package:flutter/material.dart;void main() > runApp(…...

2024台州赛CTFwp
备注: 解题过程中,关键步骤不可省略,不可含糊其辞、一笔带过。解题过程中如是自己编写的脚本,不可省略,不可截图(代码字体可以调小;而如果代码太长,则贴关键代码函数)。…...

词根plac-和place、please
英文有一个词根和单词place(v.放,放置 n.位置,地方;位,职位)长得很像,这个词根就是plac-,它有两个语义:高兴,愉悦;平静,抚平。 其实,place这个单…...

ubuntu下route命令详解
buntu下route命令详解 1、显示路由表 route -n2、临时路由设置,重启网卡失效#添加一条路由(发往192.168.62这个网段的全部要经过网关192.168.1.1)route add -net 192.168.62.0 netmask 255.255.255.0 gw 192.168.1.1#删除一条路由 删除的时候不用写网关route del …...
13.java面向对象:面向对象的三大特征
java面向对象:面向对象的三大特征 面向对象的三大特征1.封装get和set规范属性的合法化 2.继承类继承子类调用父类方法super的用法通过super调用父类public的属性super注意点super对比this 方法重写静态方法中奇怪的现象非静态方法 3.多态多态存在的条件多态中成员访…...
【VUE】Vue中的内置组件
Vue2中的内置组件: <component>:动态组件,可以根据传递的 is 属性值渲染不同的组件。<transition>:过渡动画组件,可以在元素插入、更新或移除时添加动画效果。<transition-group>:过渡动…...

若依框架篇-若依框架搭建具体过程、后端源代码分析、功能详解(权限控制、数据字典、定时任务、代码生成、表单构建、接口测试)
🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录 1.0 若依框架概述 1.1 若依构建 1.2 后端项目搭建 1.3 前端项目搭建 2.0 利用若依框架生成前后端代码案例 3.0 功能详解 3.1 功能详解 - 权限控制 3.1.1 使用权限控制…...

恢复已删除文件的 10 种安卓数据恢复工具
由于我们现在在智能手机上存储了大量重要文件,因此了解数据恢复工具变得很重要。您永远不会知道什么时候需要使用 安卓 数据恢复工具。 由于不乏 Windows 数据恢复工具,因此从崩溃的计算机中恢复文件很容易。但是,当涉及到从 安卓恢复数据时…...

Internet Download Manager2025快速下载,新功能解锁!
🌟下载界的“速度与激情”:Internet Download Manager超燃体验!🔥 嘿,各位小伙伴们!👋今天我要来给你们安利一个让我上网冲浪效率翻倍的神奇软件——Internet Download Manager(简称…...
传感器应用注意事项
一、通断型传感器 多数活动部件可直接作为导电材料的传感器为通断型传感器,在受力的条件下,其两个引脚的通断状态会发生改变。 常见通断型传感器 单通道按键多通道按键拨码开关接线帽磁力开关轻触开关… 通断型传感器无需供电,其控制环路…...

PayPal美区账号注册指南
PayPal作为一种便捷的在线支付方式,受到了广大用户的青睐。特别是对于那些需要在美国购物或者进行交易的人来说,注册并正确使用美国地区的PayPal账户显得尤为重要。本次小编会教大家如何注册和使用美区PayPal账户,并讨论是否需要“养号”的问…...

《鸟哥的Linux私房菜基础篇》---1 Linux的介绍与如何开启Linux之路
目录 一、Linux的简单介绍 1、Linux的简介 2、Linux的起源与发展 3、主要特点 4、应用场景 二、开启Linux之路 1、学习Linux的相关知识 2、正规表示法、管线命令、数据流重导向 前言 整体大纲预览 一、Linux的简单介绍 1、Linux的简介 (1)Linu…...

选择排序,插入排序,快速排序的java简单实现
代码功能 以下Java代码包含了三个排序算法的实现: 选择排序(Selection Sort):通过不断选择剩余元素中的最小值来排序数组。 插入排序(Insertion Sort):通过构建有序序列,对于未排序…...
数据库中,超出范围和溢出问题的一些处理方法
在数据库中,超出范围和溢出问题通常与数据类型、索引、以及数据存储的容量限制有关。以下是处理这些问题的一些方法: ### 1. 数据类型超出范围 **原因**: - 当尝试将超出数据类型范围的值插入到列中时,会发生错误。 **解决方法…...

Re75 读论文:Toolformer: Language Models Can Teach Themselves to Use Tools
诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文全名:Toolformer: Language Models Can Teach Themselves to Use Tools 论文下载地址:https://arxiv.org/abs/2302.04761 这篇文章是介绍tool learning的,大概来说就是…...
Android App系统签名
1.在AndroidManifest中添加 android:sharedUserId"android.uid.system" 2.获取系统签名 把以下所有文件放入同一个文件夹命名为sign 在Android系统源码中的\build\target\product\security目录下找到platform.x509.pem 和 platform.pk8两个文件; 在out/…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...