Java 并发编程:Java 线程池的介绍与使用
大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 024 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进一步完善自己对整个 Java 技术体系来充实自己的技术栈的同学。与此同时,本专栏的所有文章,也都会准备充足的代码示例和完善的知识点梳理,因此也十分适合零基础的小白和要准备工作面试的同学学习。当然,我也会在必要的时候进行相关技术深度的技术解读,相信即使是拥有多年 Java 开发经验的从业者和大佬们也会有所收获并找到乐趣。
–
在现代软件开发中,随着计算资源的增多和应用需求的复杂化,如何高效地管理并发任务成为一个关键问题。无论是处理海量数据的后台服务,还是需要实时响应的用户界面应用,都离不开多线程编程。Java 作为一种广泛使用的编程语言,提供了丰富的并发工具,其中线程池是实现高效并发的核心组件之一。
线程池通过重用现有的线程,减少了频繁创建和销毁线程的开销,同时能够有效地控制并发任务的数量,防止系统过载。借助线程池,开发者可以更方便地管理和调度任务,提高系统的响应速度和资源利用率。然而,正确理解和使用线程池并非易事,合理配置线程池参数、选择适当的拒绝策略、避免常见陷阱都是确保系统稳定运行的关键。
本文将深入介绍 Java 线程池的基本概念、常用类型及其适用场景,并结合实际代码示例展示如何在不同场景下合理使用线程池。希望通过本篇文章,读者能对 Java 线程池有一个全面的了解,并能在实际开发中应用自如,从而编写出更高效、更稳定的并发程序。
文章目录
- 1、Java 线程池介绍
- 2、Java 线程池的执行流程
- 2.1、线程池的几个重要参数
- 2.2、线程池的执行流程
- 2.3、拒绝策略
- 2.3.1、AbortPolicy
- 2.3.2、CallerRunsPolicy
- 2.3.3、DiscardPolicy
- 2.3.4、DiscardOldestPolicy
- 2.3.5、自定义拒绝策略
- 2.4、线程池状态
- 3、Java 线程池的使用
- 3.1、常用的线程池
- 3.2、Executor 框架
- 3.3、ThreadPoolExecutor创建线程池
- 3.4、Executor 框架的继承关系
1、Java 线程池介绍
池化技术现在已经屡见不鲜了,线程池、数据库连接池、Http 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。
线程池(Thread Pool)是一种基于池化思想管理线程的工具,由于创建和关闭线程需要花费时间,如果为每一个任务都创建一个线程,非常消耗资源。使用线程池可以避免增加创建和销毁线程的资源消耗,提高响应速度,且能重复利用线程。在使用线程池后,创建线程就变成了从线程池中获取空闲线程,关闭线程变成了向线程池归还线程。
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,使用完毕不需要销毁线程而是放回池中,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。他的主要特点为:线程复用、控制最大并发数、管理线程。
使用线程池的好处:
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度:当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
2、Java 线程池的执行流程
2.1、线程池的几个重要参数
线程池通过复用线程来提高性能并减少线程创建和销毁的开销。
public class ThreadPoolExecutor extends AbstractExecutorService {// 省略其他方法和实现细节.../*** 创建一个新的 {@code ThreadPoolExecutor} 实例,使用给定的初始参数。** @param corePoolSize 保持在池中的线程数量,即使它们是空闲的,除非设置了 {@code allowCoreThreadTimeOut}* @param maximumPoolSize 允许在池中存在的最大线程数量* @param keepAliveTime 当线程数量大于核心线程数时,多余的空闲线程在终止之前等待新任务的最长时间* @param unit {@code keepAliveTime} 参数的时间单位* @param workQueue 用于在任务执行前保存任务的队列。这个队列只会保存通过 {@code execute} 方法提交的 {@code Runnable} 任务* @param threadFactory 当执行器创建新线程时使用的工厂* @param handler 当执行由于线程边界和队列容量达到限制而被阻塞时使用的处理程序* @throws IllegalArgumentException 如果以下任一情况成立:<br>* {@code corePoolSize < 0}<br>* {@code keepAliveTime < 0}<br>* {@code maximumPoolSize <= 0}<br>* {@code maximumPoolSize < corePoolSize}* @throws NullPointerException 如果 {@code workQueue} 或 {@code threadFactory} 或 {@code handler} 为 null*/public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {// 检查传入参数的合法性if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();// 初始化参数this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime); // 转换 keepAliveTime 为纳秒this.threadFactory = threadFactory;this.handler = handler;// 创建线程池容器String name = Objects.toIdentityString(this);this.container = SharedThreadContainer.create(name);}// 省略其他方法和实现细节...}
在配置线程池时,有几个重要参数需要理解和配置:
-
核心线程数(corePoolSize):这是线程池中始终保持存活的线程数量。即使这些线程处于空闲状态,线程池也不会销毁它们;核心线程数决定了线程池在接收到任务时初始的并发处理能力;
-
最大线程数(maximumPoolSize):这是线程池中允许的最大线程数量。当任务队列已满且核心线程都在忙碌时,线程池会创建新线程直到达到最大线程数。这个参数决定了线程池能够处理的最大并发任务数量;
-
空闲线程存活时间(keepAliveTime):当线程池中的线程数超过核心线程数,且这些超出核心线程数的线程空闲时间超过了
keepAliveTime
,这些线程会被终止并从池中移除;这个参数有助于在任务负载减小时减少资源消耗; -
时间单位(unit):这是
keepAliveTime
参数的时间单位,如秒(TimeUnit.SECONDS
)、毫秒(TimeUnit.MILLISECONDS
)等; -
任务队列(workQueue):用于存放等待执行的任务。常见的任务队列有
LinkedBlockingQueue
、SynchronousQueue
和ArrayBlockingQueue
等;任务队列的选择会影响线程池的行为和性能。例如,SynchronousQueue
不存储任务,而是直接将任务交给工作线程处理; -
线程工厂(threadFactory):用于创建新线程。可以自定义线程工厂来设置线程的名称、优先级等。例如,可以使用
Executors.defaultThreadFactory()
来获得默认的线程工厂; -
拒绝策略(RejectedExecutionHandler):当任务队列已满且线程池中的线程数量已达到最大线程数时,线程池会执行拒绝策略。
常见的拒绝策略包括:
AbortPolicy
:抛出RejectedExecutionException
异常。CallerRunsPolicy
:由调用线程执行任务。DiscardPolicy
:直接丢弃任务,不抛出异常。DiscardOldestPolicy
:丢弃队列中最旧的任务,然后尝试提交新的任务。
配置合理的线程池参数可以有效地管理资源,提高系统的并发处理能力,并确保系统的稳定性和响应速度。在实际应用中,需要根据具体的需求和工作负载进行调优。
2.2、线程池的执行流程
线程池(ThreadPoolExecutor)的执行流程如下:
public class ThreadPoolExecutor extends AbstractExecutorService {// 省略其他方法和实现细节.../*** 在将来的某个时间执行给定的任务。任务可能在一个新的线程中执行,也可能在一个已有的线程池线程中执行。* <p>* 如果任务不能被提交执行,无论是因为这个执行器已经关闭还是因为它的容量已经达到了极限,任务都将由当前的* {@link RejectedExecutionHandler} 处理。** @param command 要执行的任务* @throws RejectedExecutionException 在 {@code RejectedExecutionHandler} 的判断下,如果任务不能被接受执行* @throws NullPointerException 如果 {@code command} 为 null*/public void execute(Runnable command) {if (command == null)throw new NullPointerException();/** 分三步进行:** 1. 如果运行的线程少于 corePoolSize,尝试启动一个新的线程并将给定的命令作为其第一个任务。* 对 addWorker 的调用会原子地检查运行状态和工作线程数,防止在不应添加线程时误报警。** 2. 如果任务能够成功入队,那么我们仍然需要再次检查是否应该添加一个线程(因为现有的线程在上次检查后可能已经终止)* 或者线程池自进入此方法以来是否已关闭。因此我们重新检查状态,如果必要的话在停止时回滚入队操作,* 或者如果没有线程则启动一个新的线程。** 3. 如果我们不能将任务入队,那么我们尝试添加一个新的线程。如果失败,我们知道要么是已经关闭,要么是已饱和,* 因此拒绝任务。*/// 获取线程池的状态和工作线程数int c = ctl.get();// 如果工作线程数小于核心线程数if (workerCountOf(c) < corePoolSize) {// 尝试添加一个新的工作线程来执行任务if (addWorker(command, true))return;c = ctl.get();}// 如果线程池在运行且任务成功入队if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();// 如果线程池不再运行且任务被成功移除if (!isRunning(recheck) && remove(command))// 拒绝任务reject(command);// 如果没有工作线程else if (workerCountOf(recheck) == 0)// 添加一个新的工作线程addWorker(null, false);// 如果不能将任务入队且添加新线程失败} else if (!addWorker(command, false))// 拒绝任务reject(command);}// 省略其他方法和实现细节...}
以下是对这个流程的详细步骤介绍:
-
任务提交:通过
execute(Runnable)
方法或submit(Callable<T>)
方法向线程池提交任务。 -
线程处理任务:线程池首先判断核心线程池中的线程数是否已达到
corePoolSize
。- 如果没有达到,创建一个新的线程来处理任务;
- 如果达到了,将任务放入工作队列
workQueue
;
-
任务队列:
- 如果工作队列未满,任务被添加到队列中等待执行。
- 如果工作队列已满,且线程池中的线程数小于
maximumPoolSize
,则创建新的线程来处理任务。 - 如果工作队列已满,且线程池中的线程数已达到
maximumPoolSize
,则执行拒绝策略(RejectedExecutionHandler
)。
-
线程执行任务:
- 线程从任务队列中取出任务并执行;
- 当线程完成任务后,会继续从队列中取出下一个任务执行。
-
线程存活时间:
- 当线程池中的线程数超过核心线程数,并且这些多余的线程空闲时间超过
keepAliveTime
,则这些线程会被终止并从池中移除。 - 这样可以避免线程池在任务负载较低时占用过多资源。
- 当线程池中的线程数超过核心线程数,并且这些多余的线程空闲时间超过
图示化线程池执行流程:
任务提交|是否可以创建核心线程/ \
是 否
/ \
创建核心线程 将任务放入工作队列|是否队列已满/ \否 是/ \放入队列 是否可以创建新线程/ \是 否/ \创建新线程 执行拒绝策略
2.3、拒绝策略
线程池的拒绝策略(RejectedExecutionHandler)用于处理当线程池无法执行新的任务时的情况。主要有四种内置的拒绝策略,分别是:
2.3.1、AbortPolicy
这是默认的拒绝策略。当线程池无法处理新的任务时,它将抛出 RejectedExecutionException
。使用场景:当你希望在任务无法被执行时立即得到通知,并采取相应的措施。
示例代码:
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
2.3.2、CallerRunsPolicy
当线程池无法处理新的任务时,该策略会让提交任务的线程直接运行这个任务。使用场景:当你希望减缓新任务的提交速度时,该策略可以让调用线程参与执行任务,从而降低任务提交的速率。
示例代码:
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
2.3.3、DiscardPolicy
当线程池无法处理新的任务时,该策略会直接丢弃被拒绝的任务,不做任何处理也不抛出异常。使用场景:当你可以接受某些任务被静默丢弃且不需要额外的处理时。
示例代码:
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
2.3.4、DiscardOldestPolicy
当线程池无法处理新的任务时,该策略会丢弃队列中最旧的任务,然后重新尝试提交被拒绝的任务。使用场景:当你希望优先处理最新的任务,可以接受丢弃一些旧任务时。
示例代码:
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();
2.3.5、自定义拒绝策略
除了上述四种内置策略外,你还可以实现 RejectedExecutionHandler
接口来创建自定义的拒绝策略。例如:
public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {// 自定义拒绝处理逻辑,例如记录日志或将任务放入另一个队列中System.out.println("Task " + r.toString() + " rejected from " + executor.toString());}
}
2.4、线程池状态
Java 线程池(ThreadPoolExecutor)的状态主要通过一个称为 ctl
的原子变量来表示,这个变量同时包含了线程池的运行状态和线程池中的有效线程数量。ctl
变量是一个 AtomicInteger
类型的变量,其高3位表示线程池的状态,低29位表示线程池中的线程数量。
线程池的状态主要有以下几种:
- RUNNING:线程池在正常运行状态,可以接受新的任务,并处理队列中的任务。
- SHUTDOWN:调用了
shutdown()
方法后,线程池进入该状态,不再接受新任务,但会继续处理队列中的任务。 - STOP:调用了
shutdownNow()
方法后,线程池进入该状态,不再接受新任务,并且会中断正在处理的任务和清空队列中的任务。 - TIDYING:所有任务都已终止,工作线程数为零,线程池将要调用
terminated()
方法。 - TERMINATED:
terminated()
方法已经完成执行,线程池完全终止。
ctl
变量的高3位表示线程池状态,低29位表示工作线程数量。以下是几个常量的定义:
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
通过这些常量的定义,可以看出线程池状态的高 3 位是如何设置的。
线程池在不同状态之间的转换流程如下:
- RUNNING -> SHUTDOWN:调用
shutdown()
方法后,线程池状态从 RUNNING 转变为 SHUTDOWN,线程池不再接受新任务,但会继续处理已提交的任务。 - (RUNNING or SHUTDOWN) -> STOP:调用
shutdownNow()
方法后,线程池状态从 RUNNING 或 SHUTDOWN 转变为 STOP,线程池不再接受新任务,并中断所有正在执行的任务。 - SHUTDOWN -> TIDYING:当所有任务都已完成,线程池状态从 SHUTDOWN 转变为 TIDYING。
- STOP -> TIDYING:当所有任务都已中断,线程池状态从 STOP 转变为 TIDYING。
- TIDYING -> TERMINATED:
terminated()
方法执行完毕后,线程池状态从 TIDYING 转变为 TERMINATED。
以下是线程池状态转换的示例代码:
public void shutdown() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();advanceRunState(SHUTDOWN);interruptIdleWorkers();onShutdown(); // hook for ScheduledThreadPoolExecutor} finally {mainLock.unlock();}tryTerminate();
}public List<Runnable> shutdownNow() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();advanceRunState(STOP);interruptWorkers();taskQueue.clear();} finally {mainLock.unlock();}return super.shutdownNow();
}
在 shutdown()
方法中,通过 advanceRunState(SHUTDOWN)
将状态转变为 SHUTDOWN
,在 shutdownNow()
方法中,通过 advanceRunState(STOP)
将状态转变为 STOP
。
3、Java 线程池的使用
3.1、常用的线程池
在 Java 中,Executors
提供了一些便捷的方法来创建常用的线程池,但在实际开发中,为了避免潜在的问题和更好地控制线程池的行为,通常建议直接使用 ThreadPoolExecutor
来创建线程池。下面是对一些常用线程池的介绍和它们可能存在的问题:
newSingleThreadExecutor:
创建一个单线程的线程池,只有一个线程在工作,所有任务按照提交顺序执行。如果唯一的线程因为异常终止,会有一个新线程替代它继续工作。使用场景:适用于需要确保顺序执行任务的场景。
代码示例:
ExecutorService executor = Executors.newSingleThreadExecutor();
newFixedThreadPool:
描述:创建一个固定大小的线程池。每次提交一个任务就创建一个线程,直到达到线程池的最大大小。线程池大小达到最大值后,将继续保持固定大小,如果某个线程因为异常终止,会补充一个新线程。使用场景:适用于需要限制并发线程数,控制资源使用的场景。
代码示例:
ExecutorService executor = Executors.newFixedThreadPool(10);
newCachedThreadPool
描述:创建一个可缓存的线程池。如果线程池中有空闲线程可以重用,则会重用空闲线程;如果没有空闲线程,则创建新线程。空闲线程会在60秒没有任务执行时被终止并移除。使用场景:适用于执行许多短期异步任务的小程序,或者负载较轻的服务器。
代码示例:
ExecutorService executor = Executors.newCachedThreadPool();
newScheduledThreadPool
描述:创建一个支持定时和周期性任务执行的线程池。可以用于需要定时执行任务的场景。使用场景:适用于需要定时或者周期性执行任务的场景。
代码示例:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);
使用 Executors 的弊端:阿里巴巴 Java 开发手册建议不要使用 Executors
创建线程池,而是直接使用 ThreadPoolExecutor
,主要是为了避免以下问题:
-
newFixedThreadPool
和newSingleThreadExecutor
:问题:使用无界的请求队列LinkedBlockingQueue
,可能会导致请求堆积,耗尽内存,甚至导致OutOfMemoryError
。 -
newCachedThreadPool
和newScheduledThreadPool
:问题:允许创建的线程数最大值为Integer.MAX_VALUE
,可能会创建大量线程,耗尽系统资源,甚至导致OutOfMemoryError
。
为了更好地控制线程池的行为,建议使用 ThreadPoolExecutor
直接创建线程池。这样可以更明确地控制线程池的核心参数,如核心线程数、最大线程数、空闲线程存活时间、任务队列等。
3.2、Executor 框架
Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor
,Executors
,ExecutorService
,ThreadPoolExecutor
,Callable
和 Future
、FutureTask
这几个类
Executor 框架是 Java5 之后引进的,在 Java 5 之后,通过 Executor 来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。
this 逃逸是指在构造函数返回之前其他线程就持有该对象的引用. 调用尚未构造完全的对象的方法可能引发令人疑惑的错误。
Executor 框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,Executor 框架让并发编程变得更加简单。
3.3、ThreadPoolExecutor创建线程池
ThreadPoolExecutor 是线程池的核心实现。线程的创建和终止需要很大的开销,线程池中预先提供了指定数量的可重用线程,所以使用线程池会节省系统资源,并且每个线程池都维护了一些基础的数据统计,方便线程的管理和监控。
通过下面的demo来了解ThreadPoolExecutor创建线程的过程。
public class TestThreadPool {public static void main(String[] args) {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 6, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<>(5));ExecutorCompletionService<String> executorCompletionService = new ExecutorCompletionService<>(threadPoolExecutor);for (int i = 0; i < 20; i++) {try {executorCompletionService.submit(()-> { try { //System.out.println("---"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } },"testtask"+i);System.out.print(" New task: testtask" + i);System.out.print(" ActiveCount: " + threadPoolExecutor.getActiveCount());System.out.print(" poolSize: " + threadPoolExecutor.getPoolSize());System.out.print(" queueSize: " + threadPoolExecutor.getQueue().size());System.out.println(" taskCount: " + threadPoolExecutor.getTaskCount());} catch (RejectedExecutionException e) {System.out.println("Reject:" + i);}try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}}threadPoolExecutor.shutdown();}
}
3.4、Executor 框架的继承关系
Java 中的线程池核心实现类是 ThreadPoolExecutor
,先通过 JDK 1.8 中 ThreadPoolExecutor
的 UML 类图,了解下 ThreadPoolExecutor
的继承关系。
ThreadPoolExecutor
实现的顶层接口是 Executor
,顶层接口 Executor
提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供 Runnable
对象,将任务的运行逻辑提交到执行器(Executor
)中,由 Executor
框架完成线程的调配和任务的执行部分。ExecutorService
接口增加了一些能力:
- 扩充执行任务的能力,补充可以为一个或一批异步任务生成
Future
的方法; - 提供了管控线程池的方法,比如停止线程池的运行。
AbstractExecutorService
则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。最下层的实现类 ThreadPoolExecutor
实现最复杂的运行部分,ThreadPoolExecutor
将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。
相关文章:

Java 并发编程:Java 线程池的介绍与使用
大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 024 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进…...

ubuntu上安装HBase伪分布式-2024年08月04日
ubuntu上安装HBase伪分布式-2024年08月04日 1.HBase介绍2.HBase与Hadoop的关系3.安装前言4.下载及安装5.单机配置6.伪分布式配置 1.HBase介绍 HBase是一个开源的非关系型数据库,它基于Google的Bigtable设计,用于支持对大型数据集的实时读写访问。HBase有…...
Mojo的特征与参数(参数化部分)详解
许多语言都具有元编程功能:即编写生成或修改代码的代码。Python 具有动态元编程功能:装饰器、元类等功能。这些功能使 Python 非常灵活且高效,但由于它们是动态的,因此会产生运行时开销。其他语言具有静态或编译时元编程功能,如 C 预处理器宏和 C++ 模板。这些功能可能受到…...
C++数组、vector求最大值最小值及其下标
使用 <algorithm> 头文件来查找数组或向量中最大值、最小值及其索引 #include <iostream> #include <vector> #include <algorithm> // 包含 std::max_element 和 std::min_elementint main() {std::vector<int> vec {3, 1, 4, 2, 5};// 查找最…...

内网安全:多种横向移动方式
1.MMC20.Application远程执行命令 2.ShellWindows远程执行命令 3.ShellBrowserWindow远程执行命令 4.WinRM远程执行命令横向移动 5.使用系统漏洞ms17010横向移动 DCOM: DCOM(分布式组件对象模型)是微软的一系列概念和程序接口。它支持不同…...

搭建 STM32 网关服务器的全流程:集成嵌入式 C++、TCP/IP 通信、Flash 存储及 JWT 认证(含代码示例)
引言 随着物联网(IoT)技术的快速发展,基于 STM32 的服务器(类似网关)在数据采集、设备控制等方面的应用越来越广泛。本文将介绍搭建一个基于 STM32 的服务器所需的技术栈,以及详细的搭建步骤和代码示例。 …...

一款免费强大的电脑锁屏工具,中文绿色免安装
这款软件主要特点是锁屏后不显示密码输入框,直接输入密码即可解锁。 ScreenBlur是一款功能强大的电脑屏幕锁软件,主要用于保护用户的隐私和数据安全。该软件的主要功能包括自动锁屏、隐藏桌面、加密锁机等。 功能特点 自动锁屏:用户可以设…...

Python | Leetcode Python题解之第319题灯泡开关
题目: 题解: class Solution:def bulbSwitch(self, n: int) -> int:return int(sqrt(n 0.5))...

前端Web-JavaScript(上)
要想让网页具备一定的交互效果,具有一定的动作行为,还得通过JavaScript来实现, 这门语言会让我们的页面能够和用户进行交互。 什么是JavaScript JavaScript(简称:JS) 是一门跨平台、面向对象的脚本语言,是…...
【积累】Python的类
类和方法的概念及实例 类 (Class):类是对具有相同属性和方法的对象集合的抽象描述。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。 方法:类中定义的函数。 构造方法 __init__():这是一个特殊的方法,会在类实例…...

Golang | Leetcode Golang题解之第318题最大单词长度乘积
题目: 题解: func maxProduct(words []string) (ans int) {masks : map[int]int{}for _, word : range words {mask : 0for _, ch : range word {mask | 1 << (ch - a)}if len(word) > masks[mask] {masks[mask] len(word)}}for x, lenX : ra…...
【感想】支持八股文在面试的应用
八股文:程序员面试中的利与弊 在现代社会的职场竞争中,尤其是IT行业,面试环节常常成为决定一个人能否入职的重要关卡。在这其中,“八股文”作为一种被广泛应用的考核工具,已经成为面试中不可或缺的一部分。然而&#…...
B - 02-计算球的体积 51Nod - 3266
对于半径为 rr 的球,其体积的计算公式为 V4/3πr3V4/3πr3 ,这里取 π3.14π3.14 。现给定 rr ,求 VV 。 Input 输入为一个不超过 100100 的非负实数,即球半径,类型为 doubledouble 。 Output 输出一个实数&#x…...
Qt pro文件详解
概述 在Qt中,.pro 文件(也称为项目文件)是Qt项目管理系统(qmake)所使用的配置文件。这个文件定义了如何构建你的Qt应用程序或库,其使用简单的键值对语法,允许你指定源文件、头文件、库依赖、配置…...

JavaFX布局-ButtonBar
JavaFX布局-ButtonBar 常用属性buttonOrderpaddingbuttonMinWidth 实现方式Java实现fxml实现 一个特殊的容器,用于创建一组按钮,水平排列按钮太多,会被遮住,不会自动产生滚动条 常用属性 buttonOrder 预制顺序 buttonBar.setBut…...

【C++程序设计】——利用数组处理批量数据(二)
👨💻个人主页:开发者-削好皮的Pineapple! 👨💻 hello 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨💻 本文由 削好皮的Pineapple! 原创 👨Ǵ…...

使用 1panel面板 部署 php网站
代码仓库:https://github.com/talmudmaster/RedCorpus 目录 网站介绍安装步骤1. 准备云服务器2. 准备域名(可跳过)3. 安装1panel面板4. 服务器开放端口5. 进入1panel面板6. 安装并启动软件(服务器和面板开放端口)7. 创…...

Windows调大虚拟内存来代替升级物理运行内存(RAM)真的有用吗?
前言 前段时间有个粉丝突发奇想说:电脑运行内存不足,调大虚拟内存来代替升级物理运行内存(内存条)不就可以了?剩下的大几百块钱吃香的喝辣的不好吗? 嗯。。。直到2024年的今天,估计还有很多小…...

[Unity] ShaderGraph实现DeBuff污染 溶解叠加效果
本篇是在之前的基础上,继续做的功能衍生。 [Unity] ShaderGraph实现Sprite消散及受击变色 完整连连看如下所示:...
java算法day28
java算法day28 300 最长递增子序列136 只出现一次的数字169 多数元素234 回文链表53 最大子数组和 300 最长递增子序列 这个是记忆化搜索的代码。是从递归改过来的。 这题显然是要用dp做比较合适。因为很容易看到原问题与子问题之间的关系。 还是从后往前看。 然后可以利用选…...

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...