理解多线程看这一篇就够了
一、基本概念与关系
程序
程序是含有指令和数据的文件,静态地存储在磁盘等存储设备上。它是软件的实体,但未被激活。
进程
进程是程序的一次执行过程,是系统运行程序的基本单位。当程序被操作系统加载并执行时,就成为一个进程,具有动态性。进程拥有独立的内存空间和系统资源(如CPU时间、内存、I/O设备等),是一个正在执行中的程序实例。进程之间相互独立,不共享内存空间。
线程
线程是进程内部的一个执行单元,是进程中实际运作的小单位,也是CPU调度的基本单位。相比进程,线程更轻量,同类线程共享同一块内存空间和系统资源。线程之间可以共享数据,但也可能相互影响,特别是在同一个进程中。线程的引入提高了程序的并发执行能力。
三者之间的关系
- 程序到进程:程序通过加载执行转变为进程,实现了从静态代码到动态执行实体的转变。
- 进程到线程:一个进程可以包含多个线程,这些线程共享进程的资源,在同一进程环境下并发执行不同的任务,提高了效率和响应速度。
- 线程相比进程,降低了资源开销,提高了通信效率,但同时也可能因为资源共享引发同步问题。
二、线程的基本状态
Java 线程在其生命周期中会经历以下6种基本状态之一,并且随着代码执行和系统调度在这些状态之间转换:
- 新建(New):线程刚被创建,尚未启动。
- 可运行(Runnable):线程可以被调度执行,包括已经获得CPU时间片正在运行的线程和等待CPU分配时间片的线程。
- 阻塞(Blocked):线程等待某个监视器锁(例如进入synchronized代码块时),或者等待I/O操作完成等。
- 等待(Waiting):线程等待其他线程执行特定操作,如调用
Object.wait()
方法,不可被中断直到等待条件满足。 - 超时等待(Timed Waiting):与等待状态相似,但线程在指定时间后会自动醒来,如调用
Thread.sleep(long millis)
方法。 - 终止(Terminated):线程已结束执行,无论是正常结束还是异常结束。
这些状态描述了线程从创建到执行完毕的完整生命周期,理解这些状态对于调试多线程程序和避免死锁等问题至关重要。
Java 线程状态如下图所示:
三、 线程池的原理与创建原因及方式
原理
线程池是一种基于池化思想管理线程的机制,其核心在于重用线程资源,减少创建和销毁线程的开销。线程池的工作流程主要包括以下几个步骤:
- 创建线程池:初始化时,预先创建一定数量的线程并将其置于待命状态,等待任务分配。
- 任务提交:当有新任务到达时,将其加入到任务队列中。
- 任务调度:线程池中的空闲线程会从任务队列中取出任务并执行。若所有线程均在忙状态,且任务队列已满,则根据策略决定是否创建新线程或拒绝任务。
- 任务执行完毕:线程不会立即销毁,而是返回线程池等待下一个任务。
- 线程管理:根据系统负载动态调整线程数量,避免过多线程导致资源耗尽或过少线程影响任务处理速度。
优点
- 资源重用:通过重复利用已创建的线程,减少了线程创建和销毁的开销。
- 提高响应速度:任务到达时无需等待新线程创建即可立即执行。
- 管理灵活性:可根据系统状况动态调整线程数量,有效控制资源使用,防止资源耗尽。
- 简化编程模型:提供统一的接口给开发者提交任务,隐藏了线程管理的复杂细节。
- 提高系统稳定性:通过控制最大线程数,防止了过多线程导致的资源竞争和调度开销,增强了系统的稳定性和可预测性。
创建线程池的方式
-
手动创建:基础方式,使用语言提供的线程创建API(如Java中的
Thread
类)配合同步机制(如队列、锁等)自行实现线程池逻辑。 -
使用标准库:
- Java: 通过
Executors
类提供的工厂方法创建不同类型线程池,如newFixedThreadPool
创建固定大小线程池,newCachedThreadPool
创建可缓存线程池等。 - C++/POSIX: 利用
std::thread
结合std::queue
等容器自建线程池,或使用第三方库如boost::thread_pool
。 - 其他语言:如Python的
concurrent.futures.ThreadPoolExecutor
,Node.js的worker_threads
模块等,都提供了线程池或类似机制的封装。
- Java: 通过
-
第三方库:使用成熟的第三方线程池库,如Java的
Apache Commons ThreadPoolExecutor
,C#的Microsoft TPL(Task Parallel Library)
等,这些库通常提供了更高级的特性,如线程池监控、任务调度策略等。
选择合适的创建方式需根据项目需求、性能要求以及目标平台的支持程度综合考虑。
四、线程池的创建与参数解析
在Java中,通过ThreadPoolExecutor
类来创建自定义线程池,其构造函数提供了高度灵活的配置选项,以便根据具体需求调整线程池的行为。下面是构造函数及其参数的详细介绍:
public ThreadPoolExecutor(int corePoolSize, // 核心线程数int maximumPoolSize, // 最大线程数long keepAliveTime, // 空闲线程存活时间TimeUnit unit, // 存活时间的单位BlockingQueue<Runnable> workQueue, // 任务队列RejectedExecutionHandler handler // 拒绝策略
)
参数解释
-
corePoolSize:核心线程数
线程池中常驻的核心线程数量。即使线程空闲,这部分线程也会保留在线程池中,不会被回收。只有在工作队列满并且当前线程数小于最大线程数时才会创建新的线程。 -
maximumPoolSize:最大线程数
线程池能容纳的最大线程数量。当活动线程达到这个数值后,新来的任务如果不能放入任务队列将被拒绝处理。 -
keepAliveTime:空闲线程存活时间
当线程池中的线程数量超过核心线程数时,多余的空闲线程在等待新任务最长时间达到keepAliveTime后,如果还没有新任务到来,那么这些线程将被终止以节省资源。 -
unit:存活时间的单位
keepAliveTime参数的时间单位,如TimeUnit.SECONDS
秒、TimeUnit.MILLISECONDS
毫秒等。 -
workQueue:任务队列
用于保存等待执行的任务的阻塞队列。不同的队列实现会影响线程池的行为,常见的有ArrayBlockingQueue
(固定大小)、LinkedBlockingQueue
(无界或有限界限)、SynchronousQueue
(直接传递,没有容量)等。 -
handler:拒绝策略
当线程池和任务队列都达到饱和状态时(即无法再接受新任务),如何处理新提交的任务。JDK内置了几种拒绝策略:AbortPolicy
:默认策略,丢弃任务并抛出RejectedExecutionException
异常。CallerRunsPolicy
:调用者运行策略,直接在调用者线程中执行被拒绝的任务。DiscardPolicy
:静默丢弃任务,不抛出任何异常。DiscardOldestPolicy
:丢弃队列中最旧的任务,并尝试重新提交当前任务。
通过调整这些参数,可以创建符合特定应用场景需求的线程池,以达到资源最优利用和任务高效执行的目的。
五、 线程池原理
1. 固定大小线程池(FixedThreadPool)
特点:
固定大小线程池维护一个固定数量的线程,确保所有任务都会在一个控制好的线程集合中执行,适合于负载较为稳定、任务执行时间相对均衡的场景。由于使用了无界队列LinkedBlockingQueue,如果任务提交速率超过处理能力,队列可能会无限增长,导致内存溢出风险。
参数分析:
-
corePoolSize:等于最大线程数,一旦创建就不会改变,保证线程数量恒定。
-
workQueue:默认为LinkedBlockingQueue,队列容量无上限。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class FixedThreadPoolDemo {public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(3);for (int i = 0; i < 10; i++) {final int taskId = i;executor.execute(() -> {System.out.println("Task ID : " + taskId + ", Thread Name : " + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}});}executor.shutdown();}
}
2. 可缓存线程池(CachedThreadPool)
特点:
可缓存线程池会根据任务的需求动态创建线程,空闲线程超过一定时间(默认60秒)则会被回收。这种线程池适合执行大量短期异步任务,能够迅速响应突发请求,但不适合长时间运行的任务,因为线程数量可能无限增长,导致资源耗尽。
参数分析:
- corePoolSize=0,初始时无核心线程。
- maximumPoolSize=Integer.MAX_VALUE,理论上允许无限增长。
- keepAliveTime=1分钟,空闲线程等待新任务的最长时间。
- workQueue=SynchronousQueue,直接传递,不保留任务。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class CachedThreadPoolDemo {public static void main(String[] args) {ExecutorService executor = Executors.newCachedThreadPool();for (int i = 0; i < 10; i++) {final int taskId = i;executor.execute(() -> {System.out.println("Task ID : " + taskId + ", Thread Name : " + Thread.currentThread().getName());});}executor.shutdown();}
}
3. 定时任务线程池(ScheduledThreadPool)
特点:
定时任务线程池用于执行定时或周期性的任务,如定时检查、定时清理等。它维护了一个固定大小的核心线程池,并使用DelayedWorkQueue作为任务队列来存放将要执行的任务。
参数分析:
- corePoolSize:线程池的基本大小,即使没有任务执行,线程也会保持存活。
- 支持schedule系列方法设定任务的执行时间或周期。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class ScheduledThreadPoolDemo {public static void main(String[] args) {ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);for (int i = 0; i < 3; i++) {final int taskId = i;executor.scheduleAtFixedRate(() -> {System.out.println("Task ID : " + taskId + ", Thread Name : " + Thread.currentThread().getName());}, 0, 1, TimeUnit.SECONDS);}}
}
以上介绍了Java中三种常用的线程池类型,通过实例展示了它们的使用方法和特性。在实际应用中,应根据具体需求选择合适的线程池类型,并考虑是否需要进一步自定义线程池参数,以达到最佳的性能与资源利用率。尽管Executors类提供了简便的工厂方法,但在生产环境中推荐直接使用ThreadPoolExecutor构造函数来实现更细致的控制,以避免潜在的资源耗尽问题
六、常用的线程池
提交一个任务到线程池中,线程池的处理流程如下:
- 1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创
建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。 - 2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如
果工作队列满了,则进入下个流程。 - 3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
1、ThreadPoolExecutor的execute()方法
public void execute(Runnable command) {if (command == null) {throw new NullPointerException();}// 如果线程数大于等于核心线程数或者线程创建失败,将任务加入队列if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {// 线程池处于运行状态并且加入队列成功if (runState == RUNNING && workQueue.offer(command)) {if (runState != RUNNING || poolSize == 0) {ensureQueuedTaskHandled(command);}} // 线程池不处于运行状态或者加入队列失败,则创建线程(创建的是非核心线程)else if (!addIfUnderMaximumPoolSize(command)) {// 创建线程失败,则采取阻塞处理的方式reject(command); // is shutdown or saturated}}
}
2、创建线程的方法:addIfUnderCorePoolSize(command)
private boolean addIfUnderCorePoolSize(Runnable firstTask) {Thread t = null;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {if (poolSize < corePoolSize && runState == RUNNING) {t = addThread(firstTask);}} finally {mainLock.unlock();}if (t == null) {return false;}t.start();return true;
}
我们重点来看第7行:
private Thread addThread(Runnable firstTask) {Worker w = new Worker(firstTask);Thread t = threadFactory.newThread(w);if (t != null) {w.thread = t;workers.add(w);int nt = ++poolSize;if (nt > largestPoolSize) {largestPoolSize = nt;}}return t;
}
这里将线程封装成工作线程worker,并放入工作线程组里,worker类的方法run方法:
public void run() {try {Runnable task = firstTask;firstTask = null;while (task != null || (task = getTask()) != null) {runTask(task);task = null;}} finally {workerDone(this);}
}
worker在执行完任务后,还会通过getTask方法循环获取工作队里里的任务来执行。 我们通过一个程序来观察线程池的工作原理:
3、创建一个线程
public class ThreadPoolTest implements Runnable {@Overridepublic void run() {try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}}
}
4、线程池循环运行11个线程:
public static void main(String[] args) {LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(5);ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, // 核心线程数10, // 最大线程数60, // 空闲线程存活时间TimeUnit.SECONDS, // 时间单位queue // 任务队列);for (int i = 0; i < 11; i++) {threadPool.execute(new Thread(new ThreadPoolTest(), "Thread" + i));System.out.println("活跃的线程数: " + threadPool.getPoolSize());if (queue.size() > 0) {System.out.println("----------------队列阻塞的线程数" + queue.size());}}threadPool.shutdown();
}
执行结果:
活跃的线程数: 1
活跃的线程数: 2
活跃的线程数: 3
活跃的线程数: 4
活跃的线程数: 5
活跃的线程数: 5
----------------队列阻塞的线程数1
活跃的线程数: 5
----------------队列阻塞的线程数2
活跃的线程数: 5
----------------队列阻塞的线程数3
活跃的线程数: 5
----------------队列阻塞的线程数4
活跃的线程数: 5
----------------队列阻塞的线程数5
活跃的线程数: 6
----------------队列阻塞的线程数5
活跃的线程数: 7
----------------队列阻塞的线程数5
活跃的线程数: 8
----------------队列阻塞的线程数5
活跃的线程数: 9
----------------队列阻塞的线程数5
活跃的线程数: 10
----------------队列阻塞的线程数5
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task
Thread[Thread15,5,main] rejected from
java.util.concurrent.ThreadPoolExecutor@232204a1[Running, pool size = 10, active
threads = 10, queued tasks = 5, completed tasks = 0]
at
java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPool
Executor.java:2047)
at
java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at
java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at test.ThreadTest.main(ThreadTest.java:17)
分析结果
观察与分析
-
线程池配置概览:创建的线程池具体配置为:核心线程数量为5个;最大线程总数为10个;关联的工作队列容量为5个任务。
-
工作队列监控:通过queue.size()方法实时监测工作队列中的任务数量,帮助理解线程池的工作状态。
-
运行机制解析:
- 初始阶段,随着任务的提交,线程池会创建核心线程直至达到5个。
- 当核心线程满载后,新任务被加入到工作队列中,直至队列也达到其容量上限5个。
- 队列饱和后,线程池会继续创建非核心线程,直至线程总数达到最大限制10个。
- 若线程数和队列均达到上限,此时线程池进入饱和状态,需依据饱和策略处理后续提交的任务。
饱和策略(RejectedExecutionHandler)调整
当线程池和队列均达到饱和,即无法接纳新任务时,JDK提供了四种预设的饱和策略处理新提交的任务:
- AbortPolicy(默认):直接抛出RejectedExecutionException异常。
- CallerRunsPolicy:将任务回退给调用线程直接执行。
- DiscardOldestPolicy:丢弃队列中最旧的任务,尝试重新提交当前任务。
- DiscardPolicy:直接丢弃新提交的任务,不抛出异常。
修改示例:现在,我们将上述示例中的饱和策略改为DiscardPolicy,即丢弃新提交的任务而不抛出异常。
public static void main(String[] args) {// ... 线程池初始化代码保持不变 ...// 设置饱和策略为DiscardPolicythreadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());// 任务提交逻辑保持不变 ...threadPool.shutdown();
}
基于之前的讨论,让我们整合并扩展这部分内容,包括修改线程池的饱和策略为DiscardPolicy
并进行相应排版:
观察与分析
-
线程池配置概览:创建的线程池具体配置为:核心线程数量为5个;最大线程总数为10个;关联的工作队列容量为5个任务。
-
工作队列监控:通过
queue.size()
方法实时监测工作队列中的任务数量,帮助理解线程池的工作状态。 -
运行机制解析:
- 初始阶段,随着任务的提交,线程池会创建核心线程直至达到5个。
- 当核心线程满载后,新任务被加入到工作队列中,直至队列也达到其容量上限5个。
- 队列饱和后,线程池会继续创建非核心线程,直至线程总数达到最大限制10个。
- 若线程数和队列均达到上限,此时线程池进入饱和状态,需依据饱和策略处理后续提交的任务。
饱和策略(RejectedExecutionHandler
)调整
当线程池和队列均达到饱和,即无法接纳新任务时,JDK提供了四种预设的饱和策略处理新提交的任务:
- AbortPolicy(默认):直接抛出
RejectedExecutionException
异常。 - CallerRunsPolicy:将任务回退给调用线程直接执行。
- DiscardOldestPolicy:丢弃队列中最旧的任务,尝试重新提交当前任务。
- DiscardPolicy:直接丢弃新提交的任务,不抛出异常。
修改示例:现在,我们将上述示例中的饱和策略改为DiscardPolicy
,即丢弃新提交的任务而不抛出异常。
public static void main(String[] args) {// ... 线程池初始化代码保持不变 ...// 设置饱和策略为DiscardPolicythreadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());// 任务提交逻辑保持不变 ...threadPool.shutdown();
}
通过这样的调整,当线程池和队列达到饱和状态后,任何新提交的任务将被默默地丢弃,这种方式适用于那些可以安全丢弃的任务场景,避免了因任务拒绝而引发的异常中断,提高了程序的健壮性。
相关文章:

理解多线程看这一篇就够了
一、基本概念与关系 程序 程序是含有指令和数据的文件,静态地存储在磁盘等存储设备上。它是软件的实体,但未被激活。 进程 进程是程序的一次执行过程,是系统运行程序的基本单位。当程序被操作系统加载并执行时,就成为一个进程&a…...
解释“this”的工作原理,原型继承如何工作,以及如何实现手写JS继承。还包括Array对象自带的方法列举,以及如何使用闭包。
1:"this"的工作原理: this 关键字指向当前执行上下文的对象,也就是当前函数被调用时所在的对象。this 的值取决于函数的调用方式,不同的调用方式会导致 this 指向不同的对象:作为对象的方法调用,this 指向该对象作为普通函数调用,this 指向全局对象(浏览器中是 wind…...

汇智知了堂实力展示:四川农业大学Python爬虫实训圆满结束
近日,汇智知了堂在四川农业大学举办的为期五天的校内综合项目实训活动已圆满结束。本次实训聚焦Python爬虫技术,旨在提升学生的编程能力和数据分析能力,为学生未来的职业发展打下坚实的基础。 作为一家在IT教育行业享有盛誉的机构ÿ…...
2024下半年软考报名人数较去年减少,仅52.77万
2024下半年软考报名人数 2024年上半年软考考试共计报考52.77万人,其中,初级资格5.12万人、中级资格24.37万人、高级资格23.28万人。 根据往年报名人数,本次考试人数是减少了的,原因分析如下: 1、原来报名热门专业系…...

【前端常见面试题整理】
开放性的题目 自我介绍 突出学习能力 我想换工作的主要原因是 介绍项目 平时是如何学习前端开发的 主要就是两个途径,一个是查阅官方文档,然后就是在网上查找技术资料或者视频去学习。平时没事的时候也会看看github,同时关注一些社区和IT网…...

Java final关键字
可以修饰类、属性、方法和局部变量。 何时使用: 1、不希望某个类被继承,用final修饰该类。 2、不希望父类的某个方法被子类覆盖/重写,用final修饰该方法。 3、不希望类的某个属性的值被修改,用final修饰该属性。 4、不希望某…...

半个月获邀请函|在读博士公派新加坡南洋理工大学联合培养
J同学计划先申报CSC联培博士,如若获批,再走本校的联培资助项目。我们仅用半个月时间,就为其申请到新加坡南洋理工大学,因导师接收名额有限制,其又热心推荐了另一位指导导师,最终J同学如愿获得学校资助出国联…...
c++移动构造和赋值的样例
#include <iostream>class MyResource { public:// 默认构造函数MyResource(size_t size 0) : m_size(size), m_data(size ? new int[size] : nullptr) {std::cout << "Default constructor called\n";}// 析构函数~MyResource() {delete[] m_data;std…...

静态测试---基于WorkList的活跃变量分析
本文主要用于记录在活跃变量分析实验中的报错及解决,涉及静态测试的详细原理内容较少,编译运行底层逻辑偏多。 一、实验要求 1)使用llvm基于框架实现一个基于WorkList的活跃变量分析demo。变量在某个程序点有两种状态,live 或 dea…...

Oracle 证书的重要性
随着信息技术的飞速发展,数据库管理已成为企业运营中不可或缺的一部分。Oracle作为全球领先的数据库管理系统提供商,其Oracle Certified Professional(OCP)认证已成为数据库管理员和开发人员追求的专业认证之一。本文将深入探讨Or…...

【Go专家编程——并发控制——Mutex】
1.Mutex 互斥锁是并发程序中对共享资源进行访问控制的主要手段,对此Go语言提供了Mutex,对外暴露Lock()和Unlock两个方法,分别用于加锁和解锁。 1.1 Mutex的数据结构 源码如下: type Mutex struct{state int32//代表互斥锁的状…...

SRE视角下的DevOps构建之道
引言: 随着数字化时代的飞速发展,软件成为了企业竞争力的核心。为了更高效地交付高质量的软件,DevOps(Development和Operations的组合)作为一种文化、实践和工具集的集合,逐渐成为了行业内的热门话题。然而…...
小白如何如何理解滑动窗口最大值问题python
文章目录 题目描述思路什么时候弹出元素什么时候加入元素 代码示例和解释 题目描述 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 举例: 输…...

Linux--进程间通信(2)(有名管道)
目录 1.原理 2.创建命名管道 3.使用命名通道实现简单的通信 4.使用创建的命名管道 1.原理 匿名管道没有名称,它们是通过句柄在父进程和子进程之间传递的。这意味着匿名管道只能用于具有父子关系的进程之间。 但如果程序之间没关系,那么这时候就要用…...

window自动启动bat文件
开机自动开启远程桌面, WinR 执行netplwiz 命令进入设置;取消勾选,可选择所需用户,点击应用,输入远程的密码即可 开机自动开启远程桌面, WinR 执行netplwiz 命令进入设置;取消勾选࿰…...

2024年蓝桥杯Web开发【大赛大纲】15届
一、 组别 Web应用开发分为:大学组和职业院校组。 每位选手只能申请参加其中一个组别的竞赛。各个组别单独评奖。 研究生和本科生只能报大学组。 其它高职高专院校可自行选择报任意组别。 二. 竞赛赛程 省赛时长:4小时。 决赛时长:4小…...

【vue-cli搭建vue项目的过程2.x】
vue-cli搭建vue项目 vue-cli搭建vue项目安装node安装vue-cli脚手架并创建项目安装 Ant Design Vue或element-ui(笔者使用Ant-design-vue组件,并全局引入)开发安装三方库包1、Package.json文件---引入如下package.json文件执行npm i或npm install命令即可下载如下依赖…...

Android 生成正式版密钥库 KeyStore
步骤1:打开生成正式版密钥库设置 点击 Build 菜单,选择 Generate Signed App Bundle or APK: 这是打开后的样子: 步骤2:选择 APK Android App Bundle 是用于上架 Google Play 商店的。 正常情况下选择 APK。 选择…...

POLARDB:新零售用户MySQL上云最佳选择
什么是云数据库POLARDB? POLARDB是阿里云自主研发的最新一代RDS关系型数据库,是特别针对互联网场景设计的Cloud-Native 云原生数据库。POLARDB for MySQL版本,在提供100%兼容MySQL5.6/8.0的关系型事务处理ACID特性之上,能够提供完…...

PHP MySQL图解学习指南:开启Web开发新篇章
PHP曾经是最流行的Web开发语言,许多世界领先的网站(如Facebook、维基百科和WordPress)都是用它编写的。PHP运行在Web服务器端,通过使用存储在MySQL数据库中的数据,使得网站可以为每一位访问者显示不同的定制页面。书中采用简单、直观的图示化…...

19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...

css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...