剖析线程池实现原理
前置推荐阅读: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/…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...
前端开发者常用网站
Can I use网站:一个查询网页技术兼容性的网站 一个查询网页技术兼容性的网站Can I use:Can I use... Support tables for HTML5, CSS3, etc (查询浏览器对HTML5的支持情况) 权威网站:MDN JavaScript权威网站:JavaScript | MDN...
ThreadLocal 源码
ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物,因为每个访问一个线程局部变量的线程(通过其 get 或 set 方法)都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,这些类希望将…...
游戏开发中常见的战斗数值英文缩写对照表
游戏开发中常见的战斗数值英文缩写对照表 基础属性(Basic Attributes) 缩写英文全称中文释义常见使用场景HPHit Points / Health Points生命值角色生存状态MPMana Points / Magic Points魔法值技能释放资源SPStamina Points体力值动作消耗资源APAction…...
python读取SQLite表个并生成pdf文件
代码用于创建含50列的SQLite数据库并插入500行随机浮点数据,随后读取数据,通过ReportLab生成横向PDF表格,包含格式化(两位小数)及表头、网格线等美观样式。 # 导入所需库 import sqlite3 # 用于操作…...
英国云服务器上安装宝塔面板(BT Panel)
在英国云服务器上安装宝塔面板(BT Panel) 是完全可行的,尤其适合需要远程管理Linux服务器、快速部署网站、数据库、FTP、SSL证书等服务的用户。宝塔面板以其可视化操作界面和强大的功能广受国内用户欢迎,虽然官方主要面向中国大陆…...
java 局域网 rtsp 取流 WebSocket 推送到前端显示 低延迟
众所周知 摄像头取流推流显示前端延迟大 传统方法是服务器取摄像头的rtsp流 然后客户端连服务器 中转多了,延迟一定不小。 假设相机没有专网 公网 1相机自带推流 直接推送到云服务器 然后客户端拉去 2相机只有rtsp ,边缘服务器拉流推送到云服务器 …...
