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做比较合适。因为很容易看到原问题与子问题之间的关系。 还是从后往前看。 然后可以利用选…...
vue实现歌词滚动效果
1.结构 <template><div class"lyricScroll"><div class"audio"><audio id"audio" src"./common/周传雄-青花1.mp3" controls></audio></div><div class"container" id"contai…...
【算法设计题】合并两个非递减有序链表,第1题(C/C++)
目录 第1题 合并两个非递减有序链表 得分点(必背) 题解 函数声明与初始化变量: 初始化合并链表的头节点: 合并两个链表: 处理剩余节点: 返回合并后的链表: 完整测试代码 🌈…...
Vue前端工程
创建一个工程化的vue项目 npm init vuelatest 全默认回车就好了 登录注册校验 //定义数据模型 const registerDataref({username:,password:,rePassword: }) //校验密码的函数 const checkRePassword(rule,value,callback)>{if (value){callback(new Error(请再次输入密…...
什么是药物临床试验?
药物临床试验是指在人体上进行的新药试验研究,旨在确定新药的疗效、安全性、药代动力学和药效学。临床试验不仅帮助确认药物是否对特定疾病或症状有效,还帮助识别和评估药物的副作用和风险。 药物临床试验(Clinical Trial,CT&…...
编译和汇编的区别
一、编译 编译是将高级语言(如C、C、Java等)编写的源代码转换成计算机可以直接执行的低级语言(通常是机器语言或汇编语言)的过程 编译 —— 将人类可读的源代码转换为计算机可执行的指令集 编译过程 通常包括词法分析、语法分…...
C# 设计倒计时器、串口助手开发
文章目录 1. 实现一个简单的倒计时器开始、暂停2. 串口助手开发 1. 实现一个简单的倒计时器开始、暂停 namespace Timer {public partial class Form1 : Form{int count;//用于定时器计数int time;//存储设定的定时值bool parse false;//控制暂停计时public Form1(){Initiali…...
图论① dfs | Java | LeetCode 797,Kama 98 邻接表实现(未完成)
797 所有可能路径 https://leetcode.cn/problems/all-paths-from-source-to-target/description/ 输入:graph [[1,2],[3],[3],[]] 题目分析,这里 class Solution {//这个不是二维数组,而是listList<List<Integer>> res new Ar…...
Mac安装nvm以及配置环境变量
安装nvm brew install nvm安装成功后会出现这样一段话: Add the following to your shell profile e.g. ~/.profile or ~/.zshrc:export NVM_DIR"$HOME/.nvm"[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh&q…...
AUTOSAR实战教程-使用DET来发现开发错误
2年之前因为在调试AUTOSAR存储协议栈的时候使用DET并没发现有用的信息,所以就武断下结论--这玩意没有用。活到老学到老吧,bug经历的多了,发现这玩意还挺有用的。说一下这个bug的背景。 在将时间同步报文改道CanTsync之后,由于这个AUTOSAR工具本身的问题以及配置工程师本身的…...
ZeroMQ(二):请求-响应模式,C和C++。
目录 请求响应基础 基本概念 工作流程 典型应用 请求-响应模式的特点 应用实例 优点 缺点 ZEROMQ C语言 2.1 服务器端代码(Reply Server) 2.2 客户端代码(Request Client) 3. 编译代码 4. 详细说明 ZEROMQ C 1. …...
wordpress code theme/漯河网络推广哪家好
功能描述:搜索功能,在输入关键字后,点击手机键盘搜索键(确定键或是开始键)触发搜索功能的实现方法。 方法其实特别简单: 首先:需要将搜索框和搜索按钮放在一个表单下,代码如下&#…...
深圳公共资源交易网招标公告/aso排名优化知识
出于对ml的兴趣,最近开始了对ml的学习,正在跟着 coursera学习ml的基础知识,在这里做一个笔记。 监督学习和非监督学习是两个基础的概念,但个人感觉并不是很简单,总有些容易混淆的感觉。 监督学习:…...
wordpress淘宝联盟/免费打广告平台有哪些
1、定义注解 2、注解的使用 这里就可以使用注解了。 资源地址:https://download.csdn.net/download/qq_22764659/87360597...
国外h5汇总网站/bt磁力搜索器
一、问题描述 在使用Pytorch训练模型的时候,程序出现卡死、没反应的现象。当卡到无法令人接受,然后强制终止运行程序的时候,得到以下的终端输出信息: xxxxxxxxxFile "/home/csx/.conda/envs/py4torch17/lib/python3.6/site-p…...
网站开发合同需要交印花税吗/自己想做个网站怎么做
2019独角兽企业重金招聘Python工程师标准>>> <session-config> <session-timeout>30</session-timeout> </session-config> 网上有人说session默认时间是20分钟,这句没有问题。但是如果小于20分钟的话需要修改注册表什么的我就不…...
做网站技术要求怎么写/网站推广排名
Pod 的生命周期 Pod 的生命周期 官方文档链接 Pod的status字段是一个PodStatus的对象,PodStatus中有一个phase字段。无论是手动创建还是通过Deployment等控制器创建,Pod对象总是应该处于其生命进程中以下几个阶段(phase)之一。 …...