線程池與ThreadPoolExecutor源碼解析(上)

一、線程池

線程池(ThreadPool)是一種線程復用的機制。它維護著若干個線程,任務來了就復用這些線程去執行,任務做完線程不會銷毀,而是回到池中等待下一個任務。

為什么要用線程池?

降低資源消耗:避免頻繁創建和銷毀線程帶來的系統開銷。

提高響應速度:任務到來時可以直接復用已有線程,無需等待新線程創建。

便于統一管理:可以統一分配、調優和監控線程,控制最大并發數,防止系統資源耗盡。

支持任務排隊和拒絕策略:可以靈活處理任務高峰和異常情況。

二、線程池的核心組成及工作流程

Java線程池的核心實現類是?ThreadPoolExecutor,其主要組成如下:

  • 核心線程數(corePoolSize):池中始終存活的線程數,即使它們處于空閑狀態也不會被銷毀。
  • 最大線程數(maximumPoolSize):池中允許的最大線程數。
  • 線程空閑時間(keepAliveTime):非核心線程空閑多久會被銷毀。
  • 時間單位(unit):keepAliveTime的時間單位。
  • 任務隊列(workQueue):用于保存等待執行任務的隊列。
  • 線程工廠(threadFactory):用于創建新線程的工廠。
  • 拒絕策略(handler):當線程池和隊列都滿了時,如何處理新任務。

線程池的工作流程

提交任務:調用execute()或submit()方法提交任務。

判斷核心線程數:如果當前線程數小于corePoolSize,創建新線程執行任務。

任務入隊:如果核心線程已滿,嘗試將任務放入隊列。

創建非核心線程:如果隊列也滿了,且線程數小于maximumPoolSize,創建非核心線程執行任務。

拒絕策略:如果線程數已達最大且隊列也滿,執行拒絕策略(如拋異常、丟棄任務等)。

線程復用:線程執行完任務后不會銷毀,而是回到池中等待下一個任務。

三、創建線程池的兩種方式:

方式一:通過?Executors?工具類(不推薦在生產環境使用)

1.?newFixedThreadPool(int nThreads)
  • 特點:創建一個固定大小的線程池。
  • 核心線程數 = 最大線程數。
  • 使用?LinkedBlockingQueue(無界隊列),可能會導致任務堆積,有OOM(內存溢出)風險。
  • 適用場景:需要控制并發線程數量的場景。
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
2.?newSingleThreadExecutor()
  • 特點:創建一個只有一個線程的線程池。
  • 核心線程數 = 最大線程數 = 1。
  • 使用?LinkedBlockingQueue(無界隊列),同樣有OOM風險。
  • 適用場景:需要保證所有任務按順序執行的場景。
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
3 newCachedThreadPool()
  • 特點:創建一個可緩存的線程池,線程數會根據任務量動態調整。
  • 核心線程數為0,最大線程數為?Integer.MAX_VALUE。
  • 使用?SynchronousQueue,任務來了如果沒有空閑線程,就直接創建新線程。
  • 風險:如果任務量巨大,會無限制地創建線程,可能導致OOM或系統資源耗盡。
  • 適用場景:執行大量短期、異步任務的場景。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
4? newScheduledThreadPool(int corePoolSize)
  • 特點:創建一個支持定時及周期性任務執行的線程池。
  • 適用場景:需要執行定時任務或周期性任務的場景。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

方式二:通過?ThreadPoolExecutor?構造函數(推薦)

ThreadPoolExecutor?是 Java 并發包(java.util.concurrent)中線程池的核心實現類。它功能強大、高度可配置,是理解和使用 Java?線程池的基礎。這是最原始、最靈活,也是生產環境中推薦使用的方式。你可以完全控制線程池的所有參數。

下面是一段ThreadPoolExecutor實現的線程池構建:

import java.util.concurrent.*;public class CreateThreadPoolDemo {public static void main(String[] args) {// 定義線程池的核心參數int corePoolSize = 2;int maximumPoolSize = 5;long keepAliveTime = 60L;TimeUnit unit = TimeUnit.SECONDS;BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10); // 使用有界隊列ThreadFactory threadFactory = Executors.defaultThreadFactory();RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); // 拒絕策略// 創建線程池ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);// 使用線程池for (int i = 0; i < 15; i++) {threadPoolExecutor.execute(() -> {System.out.println(Thread.currentThread().getName() + " is running...");});}// 關閉線程池threadPoolExecutor.shutdown();}
}

而且我們查看方式一的四種類型的線程池創建:

public static ExecutorService newFixedThreadPool(int nThreads) {// LinkedBlockingQueue 的默認長度為 Integer.MAX_VALUE,可以看作是無界的return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}public static ExecutorService newSingleThreadExecutor() {// LinkedBlockingQueue 的默認長度為 Integer.MAX_VALUE,可以看作是無界的return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}// 同步隊列 SynchronousQueue,沒有容量,最大線程數是 Integer.MAX_VALUE`
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}// DelayedWorkQueue(延遲阻塞隊列)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());
}

可以看到前三種類型都是對ThreadPoolExecutor的包裝,可以大膽猜測一下,第四種應該也是對ThreadPoolExecutor的包裝;但是在他的實現中沒有看到實例化的ThreadPoolExecutor,那就有些疑惑了。那它是如何實現的封裝ThreadPoolExecutor呢?看下面的類的關系:

可以看到ScheduledThreadPoolExecutor類中繼承了ThreadPoolExecutor,所以它通過?super?關鍵字調用了父類的構造函數。這說明定時任務線程池本質上也是一個?ThreadPoolExecutor,只是配置了特殊的參數。由此可見ThreadPoolExecutor類十分的重要,它是Executors工具類的基礎組成,而且阿里巴巴的《Java開發手冊》中強制要求不要使用?Executors?的這幾種方法來創建線程池,因為它們都存在資源耗盡的風險;因此ThreadPoolExecutor類進一步解析十分重要;

四、ThreadPoolExecutor源碼解析

1 構造方法:
 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.acc = System.getSecurityManager() == null ?null :AccessController.getContext();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}

這是其中的全參構造構造方法,其他的構造方法都是通過this對這個構造方法的調用,類似之前Executors工具類是對ThreadPoolExecutor的封裝。它首先對非合理參數進行了判斷,如果參數不合理直接拋出對應錯誤,否則初始化參數構建ThreadPoolExecutor實例;

2 execute方法:

execute方法是Java線程池(如ThreadPoolExecutor)中最核心的任務提交方法,其作用可以總結為:向線程池提交一個需要執行的任務(Runnable),由線程池中的線程來執行該任務。

具體流程如下:

當你調用executor.execute(task)時,線程池會按照如下流程處理:

  1. 判斷當前線程數是否小于核心線程數(corePoolSize)
  2. 如果是,直接創建新線程來執行任務。
  3. 如果核心線程已滿,嘗試將任務放入任務隊列(workQueue)
  4. 如果隊列未滿,任務會被緩存,等待線程池中的線程來取出并執行。
  5. 如果隊列也滿了,且線程數未達到最大線程數(maximumPoolSize)
  6. 會創建新的非核心線程來執行任務。
  7. 如果線程池已滿且隊列也滿
  8. 執行拒絕策略(如拋出異常、丟棄任務等)。

那我們看看上述流程在execute方法中是如何實現的:

public void execute(Runnable command) {// 1. 判空,防止提交null任務if (command == null)throw new NullPointerException();// 2. 獲取線程池當前狀態和線程數int c = ctl.get();// 3. 如果當前線程數小于核心線程數,優先創建核心線程執行任務if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true)) // 嘗試創建核心線程return; // 成功則直接返回c = ctl.get(); // 失敗則重新獲取ctl,繼續后續流程}// 4. 如果線程池處于RUNNING狀態且隊列未滿,任務入隊if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get(); // 入隊后再次獲取ctl,防止狀態變化// 4.1 如果線程池已關閉且任務還在隊列,移除任務并執行拒絕策略if (!isRunning(recheck) && remove(command))reject(command);// 4.2 如果線程池里沒有線程了,創建一個非核心線程來保證隊列任務能被執行else if (workerCountOf(recheck) == 0)addWorker(null, false);}// 5. 如果隊列也滿了,嘗試創建非核心線程執行任務else if (!addWorker(command, false))// 6. 如果創建失敗(線程池已滿或已關閉),執行拒絕策略reject(command);
}
3 狀態控制

如上,你可能會疑惑ctl是啥 : 它是一種原子整型成員變量,主要用來狀態控制:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int COUNT_MASK = (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;

先看其參數RUNNING,它是-1向左位移29位

在?Java?中,int?類型是?32 位,-1?的補碼是:

1111 1111 1111 1111 1111 1111 1111 1111

?左移?29 位后,低?29 位變成?0,高?3 位還是 1:

1110 0000 0000 0000 0000 0000 0000 0000

在看看ctlOf方法:

private static int ctlOf(int rs, int wc) { return rs | wc; 
}

它是實現了一個按位或運算,為啥要這樣設計呢?這是因為可以用一個int變量(ctl)同時存儲線程池的狀態和線程數,高三位存儲狀態,低29位存儲線程數,這樣可以通過一個int變量同時管理線程池狀態和線程數;

那如何獲取狀態信息和線程數呢?

private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }

如上兩種方法,runStateOf是獲取狀態信息,wokerCountOf是獲取線程數信息:

CAPACITY是1左移29位-1得到的,其二進制信息為:0001 1111 1111 1111 1111 1111?1111 1111

其中高三位全為0,低29為全為1;可以對CAPACITY適時取反后進行按位與操作獲取高三位或者低29位信息,這樣就可以單獨獲取到該線程池的狀態和線程數量信息;

更多詳細的信息如下表:

表達式二進制運算示例作用
c1110 0000 ... 0000 0101存儲復合信息(狀態+線程數)
CAPACITY0001 1111 ... 1111 1111線程數掩碼(高3位為0,低29位為1)
c & CAPACITY0000 0000?... 0000 0101提取線程數(workerCountOf(c)的實現)
~CAPACITY1110 0000 ... 0000?0000運行狀態掩碼(高3位為1,低29位為0)
c?& ~CAPACITY1110 0000?... 0000 0000提取運行狀態(runStateOf(c)的實現)
4 addWorker方法

好了,了解了狀態信息和線程信息獲取過程,還需要看一下addWorker方法,這個是用來創建線程的方法。它的兩個參數,一個是Runnable變量,一個是布爾類型變量;

/*** 嘗試創建一個新的Worker線程,并執行其第一個任務。* @param firstTask Worker線程的第一個任務,可以為null。* @param core 如果為true,則使用corePoolSize作為線程數上限;否則使用maximumPoolSize。* @return 如果成功創建并啟動了Worker,則返回true;否則返回false。*/
private boolean addWorker(Runnable firstTask, boolean core) {// 外層循環,用于在CAS失敗或線程池狀態改變時重試。retry:for (;;) {int c = ctl.get();int rs = runStateOf(c);// --- 狀態檢查 ---// 檢查是否可以添加新的Worker線程。// 如果線程池已關閉 (>= SHUTDOWN),則通常不允許添加新線程。// 但有一個例外:如果狀態是SHUTDOWN,且任務隊列不為空,允許添加一個沒有初始任務的Worker來處理隊列中的任務。if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;// 內層循環,用于通過CAS原子地增加工作線程數。for (;;) {int wc = workerCountOf(c);// --- 容量檢查 ---// 檢查工作線程數是否已達到上限 (CAPACITY或core/maximumPoolSize)。if (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))return false;// --- CAS操作 ---// 嘗試原子地將工作線程數+1。if (compareAndIncrementWorkerCount(c))break retry; // CAS成功,跳出所有循環,繼續執行后續的創建邏輯。// --- CAS失敗處理 ---c = ctl.get();  // CAS失敗,重新讀取ctl的值。if (runStateOf(c) != rs)continue retry; // 如果線程池狀態已改變,回到外層循環重試。// 如果狀態未變,說明是其他線程也增加了線程數導致的CAS失敗,僅重試內層循環。}}// --- 創建并啟動Worker線程 ---boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {// 創建一個新的Worker對象,它包裝了任務和要執行的線程。w = new Worker(firstTask);final Thread t = w.thread;if (t != null) {final ReentrantLock mainLock = this.mainLock;mainLock.lock(); // 獲取全局鎖,保證線程安全地添加Worker和啟動線程。try {// 在持有鎖的情況下,再次檢查線程池狀態,防止在獲取鎖的過程中線程池被關閉。int rs = runStateOf(ctl.get());// 如果線程池正在運行,或者處于SHUTDOWN狀態且允許添加空任務的Worker。if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {if (t.isAlive()) // 預檢,防止啟動一個已經存活的線程。throw new IllegalThreadStateException();// 將新的Worker添加到workers集合中。workers.add(w);int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;}} finally {mainLock.unlock(); // 確保鎖被釋放。}if (workerAdded) {t.start(); // 啟動線程。workerStarted = true;}}} finally {// --- 失敗回滾 ---// 如果線程啟動失敗(如ThreadFactory創建失敗或啟動過程中出錯)。if (!workerStarted) {addWorkerFailed(w); // 調用失敗處理方法,將之前增加的線程數-1,并從集合中移除Worker。}}return workerStarted;
}

先看for循環中的第一個判斷

? ?if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;

rs先判斷線程池是否是正常狀態并且任務和隊列不能為null,為啥判斷隊列不為null呢,因為,當核心線程數沒到最大值時,再來的任務會創建核心線程,當核心線程達到最大值時會向隊列中暫存任務,因此隊列不為null時,核心線程數達到了最大值,不會創建核心線程數;

在狀態檢查之后,進行線程容量檢查,如果小于核心線程數則進行增加線程數,在增加過程中使用的是cas操作,如果不成功則重新獲取rs判斷狀態;如果線程池狀態已改變,回到外層循環重試。如果狀態未變,說明是其他線程也增加了線程數導致的CAS失敗,僅重試內層循環。

當線程核心數增加成功后,開始增加worker線程并且啟動線程,在添加工作線程時使用ReentrantLock 對workers對象上鎖;在此過程中會再次檢查線程池狀態,在添加成功后同時更新largestPoolSize參數,這個參數記錄了線程池中最大的線程數量。別把核心線程數:corePoolSize和最大線程數:maximumPoolSize混淆了。

當添加線程成功則運行線程

如果線程啟動失敗則調用失敗處理方法,將之前增加的線程數-1,并從集合中移除Worker。

先寫這些吧......

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/89770.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/89770.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/89770.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux內核IP分片重組機制剖析:高效與安全的藝術

在IP網絡通信中,當數據包超過MTU限制時,路由器會將其拆分為多個分片。這些分片到達目標主機后,內核必須高效、安全地重組原始數據包。Linux內核的net/ipv4/inet_fragment.c實現了一套精妙的分片管理框架,完美平衡了性能和安全性需求。本文將深入剖析其設計哲學與關鍵技術。…

相機模型和對極幾何

一、相機模型 1.針孔相機模型-外參矩陣 1.世界坐標系到相機坐標系 世界坐標系&#xff1a;可以定義空間中任意一個位置&#xff0c;原點位置三個坐標軸方向坐標系姿態&#xff08;X,Y,Z&#xff09;相機坐標系&#xff1a;定義在相機上&#xff0c;原點是相機中心&#xff0c;z…

Git 常用命令與操作步驟

以下是 Git 常用命令與操作步驟 的整理&#xff0c;涵蓋日常開發中最核心的場景&#xff0c;適合快速查閱和上手&#xff1a;1. 初始化與克隆倉庫操作命令本地初始化倉庫git init克隆遠程倉庫git clone <倉庫URL> &#xff08;如 git clone https://gitlab.com/user/repo…

Leetcode-.283移動零

class Solution:def moveZeroes(self, nums: List[int]) -> None:"""Do not return anything, modify nums in-place instead."""pos0for i in range(len(nums)):if nums[i]!0:nums[pos],nums[i]nums[i],nums[pos]pos1本題運用雙指針來寫&…

在React中做過哪些性能優化?

1. 使用 React.memo 進行組件優化 問題:當父組件重新渲染時,子組件也會重新渲染,即使它的 props 沒有變化。 解決方案:使用 React.memo 包裹子組件,讓其只在 props 變化時才重新渲染。 const MyComponent = React.memo((props) => {// 子組件代碼 }); 2. 使用 useCa…

安裝docker可視化工具 Portainer中文版(ubuntu上演示,所有docker通用) 支持控制各種容器,容器操作簡單化 降低容器門檻

以下有免費的4090云主機提供ubuntu22.04系統的其他入門實踐操作 地址&#xff1a;星宇科技 | GPU服務器 高性能云主機 云服務器-登錄 相關兌換碼星宇社區---4090算力卡免費體驗、共享開發社區-CSDN博客 兌換碼要是過期了&#xff0c;可以私信我獲取最新兌換碼&#xff01;&a…

ansible批量部署zabbix客戶端

?ansible編寫劇本步驟 1??創建roles目錄結構2??在group_vars/all/main.yml中定義變量列表3??在tasks目錄下編寫tasks任務4??在files目錄下準備部署文件5??在templates目錄下創建j2模板文件6??在handlers目錄下編寫handlers7??在roles目錄下編寫主playbook8??運…

螞蟻數科AI數據產業基地正式投產,攜手蘇州推進AI產業落地

近日&#xff0c;螞蟻數科AI數據產業基地在太倉智匯谷科技創新園正式投產。該基地作為蘇州市首個AI數據產業基地&#xff0c;旨在通過跨行業人才與前沿技術&#xff0c;為長三角制造業、金融、醫療等領域的大模型落地提供場景化、高質量的訓練數據支撐。數據被視為AI學習的核心…

計算機的網絡體系及協議模型介紹

目錄 1、網絡協議介紹 1.1、定義 1.2、基本作用 1.3、協議的主要內容 2、網絡協議分層 2.1、協議分層原因 2.2、網絡協議分層的缺點 2.3、OSI協議和TCP/IP協議的聯系 3、TCP/IP 協議族 3.1、定義介紹 3.2、組成 1、應用層 2、運輸層 3、網絡層 3.3、底層流程 4、…

密碼管理安全防御

密碼管理是信息安全的核心環節,其目標是通過規范密碼的生成、存儲、傳輸、驗證和生命周期管理,防止未授權訪問,保護用戶賬號和系統資源的安全。以下從核心原則、技術實踐、常見問題及解決方案等方面詳細說明: 一、密碼管理的核心原則 密碼管理需遵循“安全性”與“可用性…

Java異步日志系統性能優化實踐指南:基于Log4j2異步Appender與Disruptor

Java異步日志系統性能優化實踐指南&#xff1a;基于Log4j2異步Appender與Disruptor 一、技術背景與應用場景 在高并發的后端應用中&#xff0c;日志記錄往往成為性能瓶頸之一。同步寫日志會阻塞業務線程&#xff0c;導致響應延遲&#xff1b;而簡單的異步隊列實現又可能出現積壓…

Mybatis07-緩存

一、緩存機制的原理計算機每次從mysql中執行sql語句&#xff0c;都是內存與硬盤的通信&#xff0c;對計算機來說&#xff0c;影響效率。因此使用緩存機制。1-1、MyBatis 的緩存機制&#xff1a;執行 DQL&#xff08;select 語句&#xff09;的時候&#xff0c;將查詢結果放到緩…

【機器學習深度學習】LoRA 與 QLoRA:大模型高效微調的進階指南

目錄 前言 一、LoRA&#xff1a;低秩微調的經典之作 二、QLoRA&#xff1a;效率與精度的升級版 三、LoRA vs QLoRA&#xff1a;如何選擇&#xff1f; 3.1 性能維度對比 3.2 根據「顯卡資源」選擇 3.3 根據「任務類型與目標」選擇 3.4 根據「模型規模」選擇 3.5 根據…

教育行業網絡升級最佳實踐:SD-WAN、傳統方案與混合方案對比分析

隨著教育行業的數字化轉型不斷深入&#xff0c;網絡的穩定性、靈活性和安全性成為各類教育應用&#xff08;如遠程課堂、智慧校園和教育云平臺&#xff09;的核心支撐。然而&#xff0c;傳統的 MPLS 專線方案成本高、擴展性差&#xff0c;而純 SD-WAN 的方案在極高可靠性要求的…

[黑馬頭條]-文章列表加載

目錄 1.1)需求分析 1.2)表結構分析 ap_article 文章基本信息表 ap_article_config 文章配置表 ap_article_content 文章內容表 導入文章數據庫 實現思路 接口定義 功能實現 定義接口 編寫mapper文件 編寫業務層代碼 實現類&#xff1a; 定義常量類 編寫控制器代碼 …

使用TIANAI-CAPTCHA進行行為驗證碼的生成和緩存的二次校驗

1.導入依賴&#xff1a;<dependency><groupId>cloud.tianai.captcha</groupId><artifactId>tianai-captcha-springboot-starter</artifactId><version>1.5.2</version> </dependency>2.在application.yml中配置驗證碼相關配置…

db.refresh()的重復使用和db.rollback()

db.refresh()在 SQLAlchemy 中&#xff0c;db.refresh() 用于從數據庫中重新加載對象的狀態&#xff0c;確保對象屬性與數據庫中的實際數據保持一致。下面詳細介紹其使用場景和作用&#xff1a;1.獲取數據庫生成的值當數據庫自動生成字段&#xff08;如自增 ID、默認值、觸發器…

《Web安全之機器學習入門》讀書筆記總結

目錄 一、案例總結 1、基礎知識 &#xff08;1&#xff09;第1章 通向智能安全的旅程 &#xff08;2&#xff09;第2章 打造機器學習工具箱 &#xff08;3&#xff09;第3章 機器學習概述 &#xff08;4&#xff09;第4章 Web安全基礎 2、安全案例 &#xff08;1&#…

github 近期熱門項目-2025.7.20

github 近期熱門項目-2025.7.20 GitHub 上近期熱門或趨勢項目的信息可以從多個來源獲取,包括 GitHub Trending 頁面、技術社區推薦、以及各大技術媒體的報道。以下是一些近期在 GitHub 上備受關注的項目類別和示例: 1. AI 與機器學習項目 隨著 AI 技術的快速發展,許多開源…

使用Python清理Excel中的空行和單元格內部空行:初學者指南

前言 作為數據處理人員或辦公室工作者,你可能經常遇到Excel文件中存在多余空行或單元格內有多余空行的問題。這些不必要的空白會影響數據的美觀性,更重要的是會給后續的數據分析、合并或處理帶來麻煩。本文將介紹一個簡單的Python腳本,幫助你高效地解決這些問題。 很多工具…