線程池:ThreadPoolExecutor源碼解讀

来源:https://www.cnblogs.com/knowledgeispower/archive/2022/09/03/16653042.html
-Advertisement-
Play Games

前言 Tkinter(即 tk interface) 是 Python 標準 GUI 庫,簡稱 “Tk”;從本質上來說,它是對 TCL/TK 工具包的一種 Python 介面封裝。Tkinter 是 Python 自帶的標準庫,因此無須另行安裝,它支持跨平臺運行,不僅可以在 Windows 平臺上運 ...


目錄

1 帶著問題去閱讀

1.1 線程池的線程復用原理

用戶每次調用execute()來提交一個任務,然後任務包裝成Worker對象,並且啟動一個worker線程來執行任務(任務可能會被先加入隊列),只要任務隊列不為空且worker線程沒有被中斷,線程的run()方法通過一個while迴圈,不斷去隊列獲取任務並執行,而不會進入到run()方法底部。while迴圈是線程復用的關鍵

1.2 線程池如何管理線程

首先定義兩個說明:

  • 關於獲取任務超時,會依賴以下條件:

    --1、開啟核心線程超時設置 或 線程池線程數大於核心線程數
    --2、符合1,且從workqueue獲取任務超時。(如果不符合1,則以阻塞方式獲取任務,不會超時)

  • 線程池最小保留線程數:
    --1、如果沒有開啟核心線程超時配置,則至少保留corePoolSize個線程
    --2、如果開啟核心線程超時並且當前隊列裡面還有任務,只需保留1個線程

將線程池的生命周期分為三個階段:創建階段、運行期間、終止階段
一、創建階段

  • 當線程池線程數(ctl低位)少於核心線程數(corePoolSize),創建新線程執行任務
  • 當線程池線程數大於等於核心線程數,且任務隊列未滿時,將新任務放入到任務隊列中,不創建線程
  • 當線程池線程數大於等於核心線程數(maximumPoolSize),且任務隊列已滿
    --如果工作線程數少於最大線程數,則創建新線程執行任務
    --如果工作線程數等於最大線程數,則拋出異常,拒絕新任務進入
    二、運行期間
    1、線程啟動後,將一直迴圈獲取任務並執行,只有當獲取任務超時,或者線程池被終止,才會結束。
    2、如果獲取任務超時,那麼Worker線程自然結束。此時線程池減少了1個線程。
    3、線上程結束後,線程池會檢查:1、線程池線程數<最少保留線程數 2、任務執行異常結束。如果符合,線程池會自動補充1個Worker

三、終止階段
調用shutdown()和shutdownNow()都導致線程池線程數減少。
1、shutdown()方式終止線程池:
--停止提交新的任務,已在隊列的任務會繼續執行,並且中斷空閑的Worker線程(Work.state從0->1成功),線程池狀態變為SHUTDOWN
2、shutdownNow()方式終止線程池:
--關閉線程池,不再接受新的任務,中斷已經啟動的Worker線程(Work.state>0),線程池狀態改為STOP

線程池創建線程及處理任務過程

梳理一下大概流程:

  1. 用戶線程調用execute()提交Runnable任務
  2. execute()調用addWork()將任務提交給線程池處理:如果有可用的核心線程,則提交給核心線程處理。反則,將任務先添加到任務隊列(workQueen)中。
  3. addWorker()方法將啟動一個worker線程,調用runWorker()來處理任務。
  4. runWorker()方法將迴圈獲取任務,並運行任務的run()方法來執行真正的業務。如果是以核心線程提交任務,則優先處理該任務,否則,迴圈調用getTask()來獲取任務
  5. getTask()方法,從任務隊列(workQueen)取出任務,並返回。
  6. getTask()沒有拿到任務,則執行線程結束processWorkerExit()

線程池創建階段

1.3 線程池配置的重要參數

  1. ctl:存儲線程池狀態以及線程數
  2. corePoolSize、maximumPoolSize、keepAliveTime、workQueue 參照下麵的源碼分析說明
  3. allowCoreThreadTimeOut:是否開啟核心線程超時。預設false,不在構造函數設置,需要調用方法設置
  4. HashSet workers:線程池終止時會從該集合找線程來中斷,源碼分析有說明

1.4 shutdown()和shutdownNow()區別

  • shutdown() :關閉線程池,不再接受新的任務,已提交執行的任務繼續執行;中斷所有空閑線程;將線程池狀態改為SHUTDOWN
  • ShutDownNow():關閉線程池,不再接受新的任務,中斷已經啟動的Worker線程;將線程池狀態改為STOP;返回未完成的任務隊列

1.5 線程池中的兩個鎖

  1. mainLock主鎖是可重入的鎖,用來同步更新的成員變數
  2. Worker內部實現了一個鎖,它是不可重入的,在shutdown()場景中,通過tryLock確保不會中斷還沒有開始執行或者還在執行中的worker線程。

2 源碼分析過程中的困惑及解惑

---什麼情況任務會提交失敗?
同時符合以下條件,任務才會被提交:

  1. 線程池狀態等於RUNNING狀態
  2. 如果任務隊列已經滿了,並且線程池線程數 少於 配置的線程池最大線程數(maximumPoolSize) 且小於線程池的最大支持線程數(CAPACITY)時。(如果隊列沒滿,任務將會先加入到隊列中)

特別說明:特殊情況會創建任務為空的Worker線程來幫助隊列中的任務跑完

---核心線程數的意義?從測試結果看,他決定了工作線程最大併發數,但未代碼驗證

  1. 核心線程數決定提交任務什麼時候會被放入到隊列中:即線程池線程數>=核心線程數時。
  2. 核心線程數大小跟併發執行線程(任務)無關。也就是,它不決定工作線程最大併發數
  3. 核心線程數可以動態修改。(如果增大了,可能會馬上創建新的Worker線程)

---線程池狀態不是RUNNING,或者往workQueue添加worker失敗,這是為什麼還要提交任務
以下情況會創建任務為空的Worker線程來執行隊列中的任務

  1. 當前線程池狀態為shutdown,但是任務隊列不為空,這時創建Worker線程來幫助執行隊列的任務
  2. 當前線程池狀態為running, 任務添加到隊列後,接著線程池被關閉,並且從隊列移除該任務失敗,並且線程池線程數為0,這時創建Worker線程來確保剛提交的任務有機會執行。

---為什麼runWorker()方法在執行任務前後加鎖,但是線程依然能夠併發?

  1. worker線程是通過創建Worker對象來創建的,在addWorke()的while迴圈創建了多個Worker對象,每個Worker對象都有自己的鎖,Worker線程通過runWorker()訪問的是當前對象的鎖,因此Worker線程能夠併發;
  2. 鎖的意義是限制不能中斷執行中的任務,因為主線程調用shutdown()和shutdownNow()方法時,會遍歷WorkerSet的Worker對象,調用tryLock(),這時主線程和Worker線程競爭同一個鎖。

3 源碼分析

3.1 類繼承關係

  1. Executo介面:專門提交任務,只有一個execute()方法。Executor 提供了一種將任務的提交和任務的執行兩個操作進行解耦的思路:客戶端無需關註執行任務的線程是如何創建、運行和回收的,只需要將任務的執行邏輯包裝為一個 Runnable 對象傳遞進來即可,由 Executor 的實現類自己來完成最複雜的執行邏輯
  2. ExecutorService介面:繼承了Executor,擴展執行任務的能力。例如:獲取任務的執行結果、取消任務等功能;提供了關閉線程池、停止線程池,以及阻塞等待線程池完全終止的方法,需要ThreadPoolExecutor實現
  3. AbstractExecutorServic類:實現了 ExecutorService ,是上層的抽象類,負責將任務的執行流程串聯起來,從而使得下層的實現類 ThreadPoolExecutor只需要實現一個執行任務的方法即可
  4. ThreadPoolExecutor:可以看做是基於生產者-消費者模式的一種服務,內部維護的多個線程相當於消費者,提交的任務相當於產品,提交任務的外部就相當於生產者

3.2 類的常量/成員變數

   //--------------------------常量部分------------------------
   
// 常量29。用在移位計算Integer.SIZE=32)
private static final int COUNT_BITS = Integer.SIZE - 3; //29
// 最大支持線程數 2^29-1:000 11111111111111111...
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// 以下為線程池的四個狀態,用32位中的前三位表示
 // 011 terminated() 方法執行完成後,線程池的狀態會轉為TERMINATED.
private static final int TERMINATED =  3 << COUNT_BITS;
// 010 所有任務都銷毀了,workCount=0的時候,線程池的裝填在轉換為TIDYING是,會執行鉤子方法terminated()
private static final int TIDYING    =  2 << COUNT_BITS; //翻譯為整理
// 001 拒絕新的任務提交,清空在隊列中的任務
private static final int STOP       =  1 << COUNT_BITS;
// 000 拒絕新的任務提交,會將隊列中的任務執行完,正在執行的任務繼續執行.
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// 111 00000 00000000 00000000 00000000  線程運行中 【running狀態值為負數最小】
private static final int RUNNING    = -1 << COUNT_BITS; //線程池的預設狀態


//------------------------變數部分------------------------

// ctl存儲線程池狀態和線程池大小,那麼用前3位表示線程池狀態,後29位表示:線程池大小,即線程池線程數
//線程池狀態初始值為RUNNING
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//任務隊列
//保存不能馬上執行的Runnable任務。
//執行shutdownNow()時,會返回還在隊列的任務
private final BlockingQueue<Runnable> workQueue;
// 主鎖,對workers、largestPoolSize、completedTaskCount的訪問都必須先獲取該鎖
private final ReentrantLock mainLock = new ReentrantLock();

// 包含池中的所有工作線程的集合。持有mainLock訪問  
// 創建Worker時,添加到集合
// 線程結束時,從集合移除
// 調用shutdown()時,從該集合中找到空閑線程並中斷
// 調用shutdownNow()時,從該集合中找到已啟動的線程並中斷
private final HashSet<Worker> workers = new HashSet<Worker>();

// 線程通信手段, 用於支持awaitTermination方法:等待所有任務完成,並支持設置超時時間,返回值代表是不是超時.
private final Condition termination = mainLock.newCondition();

// 記錄workers歷史以來的最大值。持有mainLock訪問
// 每次增加worker的時候,都會判斷當前workers.size()是否大於最大值,大於則更新
// 用於線程池監控的,作為重要指標
private int largestPoolSize;

// 計數所有已完成任務,持有mainLock訪問
// 每個worker都有一個自己的成員變數 completedTasks 來記錄當前 worker 執行的任務次數, 當前線worker工作線程終止的時候, 才會將worker中的completedTasks的數量加入到 completedTaskCount 指標中.
private long completedTaskCount;

// 線程工廠
private volatile ThreadFactory threadFactory;

// 拒絕策略,預設四種AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy,建議自己實現,增加監控指標
private volatile RejectedExecutionHandler handler;

// keepAliveTime和allowCoreThreadTimeOut 是關於線程空閑是否會被銷毀的配置

// 關於空閑的說明:
// 1、線程池在沒有關閉之前,會一直向任務隊列(workqueue)獲取任務執行,如果任務隊列是空的,在新任務提交上來之前,就會產生一個等待時間,期間,線程處於空閑狀態
// 2、向任務隊列獲取任務用:workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS),表示阻塞式獲取元素,等待超時,則終止等待並返回false。通過判斷poll()方法是true/falle來判定線程是否超時

// 獲取任務的等待時間 ,以下兩種情況會使用到該值
//1、如果啟用allowCoreThreadTimeOut,那表示核心線程的空閑時間  
// 2、當線程池內線程數超過corePoolSize,表示線程獲取任務的等待時間
private volatile long keepAliveTime;

// 核心線程是否開啟超時
// false:表示核心線程一旦啟動,會一直運行,直至關閉線程池。預設該值
// true:表示核心線程處於空閑且時間超過keepAliveTime,核心線程結束後,將不再創建新線程
// (預設的構造函數沒有設置這個屬性,需要手工調用allowCoreThreadTimeOut()方法來設置)
private volatile boolean allowCoreThreadTimeOut; 

//核心線程數量
//核心線程是指:線程會一直存活線上程池中,不會被主動銷毀【如果核心線程開啟超時,有可能被被銷毀】。
private volatile int corePoolSize;

// 配置的線程池最大線程數
private volatile int maximumPoolSize;

// 預設拒絕策略 AbortPolicy
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();

//  安全控制訪問(主要用於shutdown和 shutdownNow方法
private static final RuntimePermission shutdownPerm =  new RuntimePermission("modifyThread");

// 在threadPoolExecutor初始化的時候賦值,acc對象是指當前調用上下文的快照,其中包括當前線程繼承的AccessControlContext和任何有限的特權範圍,使得可以在稍後的某個時間點(可能在另一個線程中)檢查此上下文。
private final AccessControlContext acc;

3.3 成員變數訪問方法

// 獲取當前線程池的狀態(前3位)
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 獲取當前線程池中線程數(後29位)
private static int workerCountOf(int c){ return c & CAPACITY; }
// 更新狀態和數量
private static int ctlOf(int rs, int wc) { return rs | wc; }
// 小於判斷C是不是小於S,比如runStateLessThan(var,STOP),那var就只有可能是(RUNNING,SHUTDOWN)
private static boolean runStateLessThan(int c, int s) {
    return c < s;
}
// 是不是C >= S
private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}
// 判斷狀態是不是RUNNING
private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}

關於-1<<29說明

-1 << COUNT_BITS
這裡是-1往左移29位,稍微有點不一樣,-1的話需要我們自己算出補碼來
-1的原碼
10000000 00000000 00000000 00000001
-1的反碼,負數的反碼是將原碼除符號位以外全部取反
11111111 11111111 11111111 11111110
-1的補碼,負數的補碼就是將反碼+1
11111111 11111111 11111111 11111111
關鍵了,往左移29位,所以高3位全是1就是RUNNING狀態
111 00000 00000000 00000000 00000000

3.4 構造函數

//corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue 這五個參數必須指定
//最多參構造函數
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;//預設使用SynchronousQueue<Runnable>
        this.keepAliveTime = unit.toNanos(keepAliveTime); //預設60S
        this.threadFactory = threadFactory; //預設使用DefaultThreadFactory
        this.handler = handler;
    }

構造函數總結:
初始化:corePoolSize(核心線程池大小)、maximumPoolSize(線程池容納最大線程數)、workQueue(任務隊列)、threadFactory(線程工廠)、keepAliveTime(空閑線程存活時長)、handler(拒絕策略)AccessControlContext

3.5 靜態內部類Worker

3.5.1 Worker繼承關係

private final class Worker extends AbstractQueuedSynchronizer implements Runnable  {
}
  • --Worker繼承於AbstractQueuedSynchronizer

Worker繼承於AQS 為的就是自定義實現不可重入的特性(所以沒有使用 synchronized 或者 ReentrantLock)來輔助判斷線程是否處於執行任務的狀態:在開始執行任務前進行加鎖,在任務執行結束後解鎖,以便在後續通過判斷 Worker 是否處於鎖定狀態來得知其是否處於執行階段

  • -- Worker實現Runnable介面

Worker實現Runnable介面,線程是通過getThreadFactory().newThread(this) 來創建的即將 Worker 本身作為構造參數傳給 Thread 進行初始化,所以在 thread 啟動的時候 Worker 的 run() 方法就會被執行。

關於ThreadFactory說明

public interface ThreadFactory {
    Thread newThread(Runnable r);
}

3.5.2 Worker源碼分析

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        private static final long serialVersionUID = 6138294804551838833L;
       //線程類型的屬性:thread,線程池啟動工作線程,就是啟動這個thread。
       // 1、通過this.thread=getThreadFactory().newThread(this),初始化了屬性thread,this就是指Worker對象
       //2、因為Worker類實現了Runnable介面,所以thread啟動後,會運行Worker的run()方法,然後就去執行runWorker(this)方法
        final Thread thread;
        //線程要執行的第1個任務(可能為 null)  它表示這個任務立即執行,不需要放到任務隊列。在工作線程數<核心線程數時,這種場景會出現
        Runnable firstTask;
       //保存Worker線程池執行過的任務數,在runWorker()的finally中累加更新。任務執行成功與否都會更新
        volatile long completedTasks;

        Worker(Runnable firstTask) {
            setState(-1); // AQS父類的state。設為-1
            this.firstTask = firstTask;  //firstTask賦初值
            this.thread = getThreadFactory().newThread(this); //屬性thread賦值
        }

        //Runnable run方法實現
        public void run() {
            runWorker(this); //調用runWorkder方法:將Worker對象傳遞給調用者,這樣就可以訪問firstTask、thread等屬性以及lock()相關方法
        }
        
        // state 的值說明
        // -1:worker初始化;  1 :鎖被獨占; 0:鎖空閑
        
        //是否持有鎖 AQS父類方法的實現 
        protected boolean isHeldExclusively() {
            return getState() != 0; 
        }
        //以獨占方式獲取鎖,將state設為1  AQS父類方法的實現 
        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false; //假如state=1,那麼cas失敗,返回false,線程就會進入AQS隊列等待
        }
        //釋放鎖。state設為0  AQS父類方法的實現 
        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(); }

        //向線程發起中斷請求
        // 符合:1、運行中的;2、沒有處於中斷   才能中斷
        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

Worker類總結:

  1. 所謂的線程池,其實就是正在運行的多個Worker線程。
  2. Worker作為線程啟動後,它實際執行的是通過execute()提交的Runnable任務(實際業務),worker線程通過一個while迴圈來不斷獲取並任務,從而達到線程復用的效果
  3. firstTask:線程要執行的第1個任務(可能為 null) 它表示這個任務立即執行,不需要放到任務隊列。在 1、線程數<核心線程數 2、隊列已滿且線程池不在運行狀態 這兩個場景下。

4 重要方法詳解

4.1 execute()方法

execute()用來提交要運行的任務

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get(); // 計算當前線程池的狀態及線程數
        // 1、線程池線程數小於配置的核心線程數
        if (workerCountOf(c) < corePoolSize) {
         // 將任務提交給核心線程處理
            if (addWorker(command, true)) 
                return;
            //失敗的情況:1、線程池已經被關閉、2、線程池線程數大於等於核心線程數 (不能以true的方式提交了 )
            c = ctl.get(); // 重新獲取線程池狀態
        }
        
        // 2、無空閑核心線程,將任務加入隊列
        
        // 再次確認線程池為RUNNING狀態,將任務加入隊列【非阻塞式,隊列滿了會立即返回false】
        if (isRunning(c) && workQueue.offer(command)) {
            //任務加入隊列成功
            int recheck = ctl.get() ;//再次獲取當前線程池狀態(線程池可能被其它線程關閉了)
            //判斷當前線程池狀態是不是RUNNING狀態,不是就從workQueue中刪除command任務
            if (! isRunning(recheck) && remove(command))
                reject(command);//執行拒絕策略
              //如果當前線程數是0(那證明還沒有其他工作線程去處理這個任務),那麼剛剛的任務肯定在阻塞隊列裡面了,這
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);//開啟一個沒有任務的Worker線程去執行隊列的任務
        }
        
        // 3 workQueue添加worker失敗,即隊列滿了
        //創建非核心線程並執行任務
        else if (!addWorker(command, false))    //如果線程創建失敗,說明要麼是線程池當前狀態!=RUNNING,或者是任務隊列已滿且線程總數達到最大線程數了

            reject(command);//執行拒絕策略.
    }

execute()總結

  1. 進行三次addWorker的嘗試:
  2. addWorker(command, true):創建任務並以核心線程執行
  3. 核心線程數達到上限, 創建任務添加到任務隊列,不創建線程
  4. addWorker(null, false) :任務添加到隊列後,接著線程池被關閉,並且從隊列移除該任務失敗,並且線程池線程數為0,這時創建任務並以非核心線程執行
  5. addWorker(command, false) :任務隊列已滿,創建非核心線程並執行
  6. 任務提交失敗情況:線程池非RUNNING狀態 並且 任務隊列已滿並且線程池線程數達到最大線程數(maximumPoolSize)

4.2 addWorker()方法

//TERMINATED >TIDYING > STOP > SHUTDOWN > RUNNING  
//創建新的線程執行當前任務 
//firstTask: 指定新增線程執行的第一個任務或者不執行任務
private boolean addWorker(Runnable firstTask, boolean core) {
        //外迴圈:
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

           // 如果線程池狀態是SHUTDOWN、STOP、TIDYING、TERMINATED就不允許提交。
           // && 後面的特殊情況,線程池的狀態是SHUTDOWN並且要要執行的任務為Null並且隊列不是空,這種情況下是允許增加一個線程來幫助隊列中的任務跑完的,因為shutdown狀態下,允許執行完成阻塞隊里中的任務 

            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&  //execute()有addWorkder(null,false)的場景
                   ! workQueue.isEmpty()))
                return false;
            //內迴圈:cas修改工作線程數,同時判斷能否添加work
            for (;;) {
                int wc = workerCountOf(c);
                //添加任務前,線程池線程數已達到上限,此時不允許添加。上限分這三種情況:
                // 1、最大支持線程數
                // 2、以core=true提交時,配置的核心線程數。(返回false後,會以core=false再提交一次)
                // 3、以core=false提交時,配置的線程池可容納最大線程數。
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize)) //使用core則上限為核心線程數,否則最大線程數
                    return false;
               //沒超過上限,通過CAS的方式增加worker的數量(+1),增加成功就跳出外層迴圈
               if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  //獲取最新的線程池狀態,與剛開始的狀態比較
                  // - 變了,就從外層迴圈重新執行,重新進行狀態的檢查。
                // - 沒變,從當前迴圈重新執行,重新執行CAS操作。
                if (runStateOf(c) != rs) 
                    continue retry;
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            //創建Worker,並給firstTask賦初值
            w = new Worker(firstTask);
            final Thread t = w.thread; //拿到屬性thread
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock(); //此處加鎖:因為涉及屬性:workers、largestPoolSize(可能) 更新
                try {
                    int rs = runStateOf(ctl.get()); //獲取線程池最新狀態

                    if (rs < SHUTDOWN || //如果當前狀態是<SHUTDOWN也就是RUNNING狀態
                        (rs == SHUTDOWN && firstTask == null)) { //或者狀態是SHUTDOWN並且當前任務是空的(比如前面說的場景:阻塞隊裡裡面還有,但當前已經是不允許提交的狀態了)
                        if (t.isAlive()) //  檢查Worker線程已經開始跑了。(thread.start()變為alive)
                            throw new IllegalThreadStateException(); 
                        workers.add(w);  //增加worker
                        int s = workers.size(); //獲取最新worker的總數,比較並更新largestPoolSize
                        if (s > largestPoolSize)
                            largestPoolSize = s; 
                        workerAdded = true;  //表示添加worker成功   
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    //啟動worker線程。該線程會一直迴圈執行getTask(),直至返回null,線程才結束
                    t.start(); //執行runWorker()
                    workerStarted = true; //表示線程已經跑起來了
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);//worker線程沒成功啟動,進入失敗處理邏輯
        }
        return workerStarted;//;返回當前worker是否啟動成功。
    }

addWorker()總結:

  1. 檢查線程池狀態以確定能否提交任務
  2. 校驗能否以核心線程的方式提交任務
  3. 線程池的狀態是SHUTDOWN並且任務隊列不是空,允許增加一個線程來幫助隊列中的任務跑完,但不會提交任務
  4. 更新線程池線程數
  5. 超過線程池線程數峰值則更新峰值(largestPoolSize)
  6. 加鎖(mainLock)來更新
  7. 啟動worker線程

4.3 runWorker()方法

//執行任務
final void runWorker(Worker w) {
        Thread wt = Thread.currentThread(); //runWorker()是由Worker.run()調用,因此wt就是worker線程
        Runnable task = w.firstTask;  //拿到firstTask並賦值給局部變數task
        w.firstTask = null;  //firstTask置空
        w.unlock(); // 將state設置為0。因為構造函數設成-1,在執行任務前置為0。
        boolean completedAbruptly = true;//標識任務是不是立刻就完成了。
        try {
            //迴圈:先執行firstTask(不為空),後續通過getTask()獲取任務。
            while (task != null || (task = getTask()) != null) {
                 //任務執行前加鎖,任務完成後解鎖。
                 //任何地方可通過判斷鎖狀態來確認worker是否執行中
                w.lock();  //加鎖。防止任務在執行過程中被中斷。
                //判斷目的:確保線程池當狀態值大於等於 STOP 時有向線程發起過中斷請求【調用了shutdownNow()】
                 // 兩種情況:
                //1)如果當前線程池的狀態是>=Stop的,並且當前線程沒有被中斷,那麼就要執行中斷。
                //2)或者當前線程目前是已中斷的狀態並且線程池的狀態也是>=Stop的(註意Thread.interrupted是會擦除中斷標識符的),那麼因為中斷標識符已經被擦除了,那麼!wt.isInterrupted()一定返回true,這個時候還是要將當前線程中斷。第二次執行runStateAtLeast(ctl.get(), STOP)相當於一個二次檢查
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();//中斷worker線程 。因為線程池將要終止了,所以這裡沒有從workerSet移除當前線程
                try {
                    beforeExecute(wt, task);//前置操作,空方法,可以業務自己實現
                    Throwable thrown = null;
                    try {
                        //執行任務:就是執行通過execute()提交的Runnable
                        task.run();//第一個是firstTask,後面的是通過getTask()拿到的任務
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);//後置操作,空方法,可以業務自己實現
                    }
                } finally {
                    task = null;//最後將task置為null,觸發while迴圈的條件getTask()
                    w.completedTasks++; //已完成的任務計數器+1 
                    w.unlock();//釋放當前線程的獨占鎖
                }
            }
            completedAbruptly = false;  //當第一個try的代碼塊有異常, completedAbruptly = false 不生效。最後completedAbruptly為true表示發生未知異常了
        } finally {
            //getTask返回null時,執行任務退出
            processWorkerExit(w, completedAbruptly);//completedAbruptly=true表示是突然退出的
        }
    }

runWorker()總結

  1. 執行任務前先判斷線程池是否是STOPING狀態,是則中斷worker線程。
  2. 執行任務:先執行firstTask,再從任務隊列獲取執行
  3. 如果沒有任務,調用processWorkerExit()來執行線程退出的工作。
  4. 只要還有任務,worker線程就一直執行任務,並刷新completedTasks

4.4 getTask()方法

private Runnable getTask() {
        boolean timedOut = false;
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            
            //1、先判斷能否獲取到任務
            
            // 1)如果線程池的狀態是>=STOP狀態,這個時候不再處理隊列中的任務,並且減少worker記錄數量,返回的任務為null,這個時候在runRWorker方法中會執行processWorkerExit進行worker的退出操作.
            // 2)如果線程池的狀態是>=SHUTDOWN並且workQueue為空,就說明處於SHOTdown以上的狀態下,且沒有任務在等待,那麼也屬於獲取不到任務,getTask返回null.

            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();//扣減線程池線程數,在processWorkerExit()處理線程退出
                return null;
            }
            
            int wc = workerCountOf(c);//獲取當前wokrer的數量

            //以下涉及空閑線程是否會被線程池銷毀的處理邏輯

           // 線程超時處理前置條件:開啟核心線程超時 或 線程池線程數大於核心線程數
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            //線程超時處理的進一步判斷:
            // 線程池線程數超過maximumPoolSize 或者 線程設置允許超時且當前worker取任務超時
            //並且 
            // 線程池大小不是零或阻塞隊列是空的),這種就返回null,並減少線程池線程計數
        
            // 1、 (wc>maximumPoolSize)  && (wc>1)  一般情況,線程池線程數會少於配置的最大線程數,但在addWork中 狀態=shutdown且隊列不為空時,會創建一個Worker,此時可能導致wc>maximumPoolSize,這裡同時限定wc>1。因此線程池減少1個線程也不影響任務的執行【processWorkerExit()會保證還有任務就至少留有1個worker線程】。
            // 2、 (wc>maximumPoolSize) && (workQueue.isEmpty()) 沒有任務了,扣減更不影響
            // 3 、(timed && timedOut) && (wc > 1) 超時了,先扣減再說
            // 4 、(timed && timedOut) && (workQueue.isEmpty()) 超時了&隊列沒有任務,必須要扣減
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                //這裡為啥不用decrementWorkerCount()呢,上面使用decrementWorkerCount()是因為確定不管是什麼情況下,數量都要減,多減一次也沒事,因為這個時候就是要關閉線程池釋放資源
                //這裡不一樣,線程池的狀態可能是RUNNING狀態,多減一次,可能導致獲取不到worker去跑
                if (compareAndDecrementWorkerCount(c))
                    return null;  //扣減線程池線程數,在processWorkerExit()處理線程退出
                continue;//扣減失敗, 跳出本次迴圈重新檢查
            }
            //從隊列中獲取任務
            //符合【線程超時處理前置條件】時用poll設置超時時間,不符合就使用take(阻塞直至有返回)
            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r; //task不為空,此處返回task
                timedOut = true;  // 此處,r == null,肯定是poll操作超時了(註意,不代表隊列空了),繼續for迴圈,回到if ((wc > maximumPoolSize || (timed && timedOut)) 這個地方退出迴圈
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
    
     private void decrementWorkerCount() {
        do {} while (! compareAndDecrementWorkerCount(ctl.get()));
    }

getTask()總結:

  1. workQueue中獲取一個任務並返回
  2. 沒有獲取到任務就扣減線程池線程數。獲取不到任務的四種情況:
    1. 線程池的狀態是>=STOP
    2. 線程池的狀態是SHUTDOWN並且任務隊列為空
    3. 獲取任務超時
    4. 線程池線程數大於maximumPoolSize並且隊列為空

4.5 processWorkerExit()方法

//worker線程沒有拿到任務,成為空閑線程。該方法對空閑線程進一步處理
private void processWorkerExit(Worker w, boolean completedAbruptly) {
    //如果completedAbruptly為true,則說明線程執行時出現異常,需要將workerCount數量減一
    //如果completedAbruptly為false,說明在getTask方法中已經對workerCount進行減一,這裡不用再減
    if (completedAbruptly)
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        //更新已完成任務的數量的統計項
        completedTaskCount += w.completedTasks;
        //從worker集合中移除該worker
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    //嘗試關閉線程池,但如果是正常運行狀態,就不會關閉
    tryTerminate();

    int c = ctl.get();
    
    if (runStateLessThan(c, STOP)) {//1、線程池是SHUTDOWN或RUNNING(如果不是這兩個狀態,說明線程已經停止了,不做任何操作)
        if (!completedAbruptly) {//2、線程正常結束
           // 如果沒有開啟核心線程超時配置,則至少保留corePoolSize個線程;
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && !workQueue.isEmpty())//如果允許核心線程超時並且當前隊列裡面還有任務沒跑,必須留1個線程,不能全死掉.
                min = 1;
            // 如果線程池數量>=最少預留線程數
            if (workerCountOf(c) >= min)
                return; // 線程自然結束了,不用補充worker
        }
        // 1、執行任務異常結束的,補充worker
        // 2、如果線程池數量<最少預留線程數,補充worker
        addWorker(null, false);//異常結束 增加worker
        //註: 別問我為啥上面要刪除worker,還要再加,不刪是不是不用加了. 明確下那個任務已經退出getTask那塊的死迴圈了,永遠回不去了,只能新增worker.
    }
}

processWorkerExit()方法總結!!!!!:

  1. 當Worker線程結束前,完成以下工作:扣減線程池線程數(ctl)、更新已完成任務數(completedTaskCount)、Worker集合中移除一個Worker(workers)、嘗試終止線程池、計算線程池的最少保留線程數、根據最少保留線程數來確定是否補充一個Worker
  2. 關於最少保留線程數:如果沒有開啟核心線程超時配置,則至少保留corePoolSize個線程;如果開啟核心線程超時並且當前隊列裡面還有任務,只需保留1個線程
  3. 需要補充worker的兩種情況:1、線程池線程數<最少保留線程數 2、任務執行異常結束

4.6 tryTerminate()方法

//嘗試終止線程池
final void tryTerminate() {
        for (;;) {  //cas自旋 確保更新成功
            int c = ctl.get();
            //RUNNING狀態,不能終止線程池
            //線程池狀態是TIDYING或TERMINATED說明線程池已經處於正在終止的路上,不用再終止了.
            //狀態為SHUTDOWN,但是任務隊列不為空,也不能終止線程池
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
                
                //調用shutdown()或者shutdownNow()方法時,執行以下處理
                
                //工作線程數量不等於0,中斷一個空閑的工作線程並返回
                //這個時候線程池一定是 1、STOP的狀態或者 2、SHUTDOW且隊列為空  這兩種情況中斷一個空閑worker
            if (workerCountOf(c) != 0) { 
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // 設置線程池狀態為TIDYING,如果設置成功,則調用terminated()
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        terminated(); //鉤子方法,子類實現。預設什麼都不做
                    } finally {
                    // 設置狀態為TERMINATED
                        ctl.set(ctlOf(TERMINATED, 0));
                        termination.signalAll(); //喚醒阻塞等待的線程 (future的場景)
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

tryTerminate()總結

  1. 嘗試終止線程池
  2. 不能終止線程池
    1. 狀態是RUNNING,不能直接終止(如果是調用shutdown(),shutdownNow(),會先將狀態改為SHUTDOWN)
    2. 狀態是TIDYING或者TERMINATED,不能終止(因為已經處於終止過程中)
    3. 狀態是SHUTDOWN並且任務隊列不為空,不能終止(因為還有任務要處理)
  3. 可以終止線程池
    1. 狀態是SHUTDOWN並且任務隊列為空
    2. 狀態是STOP
  4. 符合可以終止線程池的條件下,如果線程池線程數不等於0,那就中斷1個Worker線程,不修改線程池狀態
  5. 符合可以終止線程池的條件下,並且線程池線程數等於0,那就將線程池狀態改為TIDYING,執行完鉤子方法terminated()後狀態再改為TERMINATED
interruptIdleWorkers(ONLY_ONE); 是否好奇為啥這裡只中斷一個worker呢, 這裡就涉及到了線程池的優雅退出了.
當執行到 interruptIdleWorkers(ONLY_ONE) 前面的時候, 線程池只能處於兩種狀態:
1) STOP 狀態 , 這個時候 workQueue 可能是有值的 , workQueue 在清空的過程中了.
2) SHUTDOWN 狀態並且 workQueue 是空的 .
這兩種狀態都是說明, 線程池即將關閉, 或者說空閑的線程此時已經沒用了,這個時候隨手關一個, 反正要關,早關晚關而已.

4.7 interruptIdleWorker()方法

//中斷一個或多個線程
private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        //遍歷worker,根據onlyOne判斷,如果為ture只中斷一個線程
        for (Worker w : workers) {
            Thread t = w.thread;
            //線程沒有被中斷並且線程是空閑狀態
            //通過tryLock實現:不能中斷還沒有開始執行或者還在執行中的worker線程。
            //線程未啟動:-1 ,線程正在執行:1  ,trylock:0->1 ; 
            
            if (!t.isInterrupted() && w.tryLock()) { 
                try {
                    t.interrupt(); //中斷操作,之後該線程就結束了
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

interruptIdleWorker()總結:

  1. 從worker集合中遍歷並中斷worker線程
  2. 只有worker線程狀態是0的,才能夠中斷不能中斷未啟動或者還在執行中的Worker線程

4.8 shutdown()方法

//初始化一個有序的關閉,之前提交的任務都會被執行,但是新提交的任務則不會被允許放入任務隊列中。如果之前被調用過了的話,那麼再次調用也沒什麼用
public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock(); //mainLock是全局變數,加鎖確保不會併發關閉線程池
        try {
            checkShutdownAccess();//安全策略判斷。方法檢查每一個線程池的線程是否有可以ShutDown的許可權。
            advanceRunState(SHUTDOWN); //CAS自旋把ctl中的狀態從RUNNING變為SHUTDOWN
            interruptIdleWorkers();//中斷所有空閑線程
            onShutdown(); // 方法告知子類,線程池要處於ShutDown狀態了 ,ScheduledThreadPoolExecutor預留的鉤子
        } finally {
            mainLock.unlock();
        }
        tryTerminate();//嘗試終止線程池
    }

shutdown()方法總結

  1. 執行shutdown()方法:關閉線程池,不再接受新的任務,已提交執行的任務繼續執行
  2. 調用interruptIdleWorkers()先中斷所有空閑線程
  3. 調用tryTerminate()嘗試終止線程池
  4. shutdown()將線程池狀態改為SHUTDOWN但不是STOP

4.9 shutdownNow()方法

//關閉線程池,不再接受新的任務,正在執行的任務嘗試終止
public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);//線程池的狀態置為STOP
            interruptWorkers();
            tasks = drainQueue(); //將剩餘任務返回
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
    private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) //迴圈所有的worker
                w.interruptIfStarted();//已經啟動的線程直接執行中斷
        } finally {
            mainLock.unlock();
        }
    }
     void interruptIfStarted() {
            Thread t;
            //只有剛剛構建的worker的時候,狀態state值是-1(這裡也能體現剛構建的worker無法被中斷),其他情況都是>=0的
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }

ShutDownNow()方法總結

  1. 關閉線程池,不再接受新的任務,中斷已經啟動的Worker線程
  2. 將線程池狀態改為STOP
  3. 返回未完成的任務隊列

4.10 isShutdown()方法

確認線程池是否關閉。判斷狀態是不是RUNNING.

public boolean isShutdown() {
        return ! isRunning(ctl.get());
    }

4.11 prestartCoreThread()方法

public boolean prestartCoreThread() {
        return workerCountOf(ctl.get()) < corePoolSize &&
            addWorker(null, true);
    }
  1. 啟動一個空閑的線程作為核心線程
  2. 如果核心線程數已到閾值, 會加入失敗, 返回false, 如果線程池處於SHUTDOWN以上的狀態也返回false
  3. 只有真正這個線程調用start方法跑起來, 才會返回true

4.12 prestartAllCoreThreads()方法

啟動所有核心線程,使他們等待獲取任務

public int prestartAllCoreThreads() {
        int n = 0;
        while (addWorker(null, true))//null代表空閑線程,true代表是增加的是核心線程
            ++n;//死迴圈增加空閑 worker 而已
        return n;
    }

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Array.apply(null, { length: 1000 }) 點擊打開視頻講解更加詳細 在閱讀VueJS教程時有這麼段demo code: render: function (createElement) { return createElement('div', Array.apply( ...
  • JavaScript 對象 對象 在JavaScript中,對象是一組無序的相關屬性和方法的集合,所有的事物都是對象,例如字元串、數值、數組、函數等。 對象是由屬性和方法組成的。 屬性:事物的特征,在對象中用屬性來表示(常用名詞) 方法:事物的行為,在對象中用方法來表示(常用動詞) 保存一個值時,可 ...
  • 網頁偽靜態 1.什麼是偽靜態網頁? 偽靜態是相對於靜態文件來說的,例如https://www.cnblogs.com/hesujian/p/11165818.html 將一個動態網頁偽裝成靜態網頁 將url地址模擬成html結尾的樣子,看上去像是一個靜態文件,只是改變了URL的表現形式,實際上還是動 ...
  • 使用pyhive的時候出現了這個問題,我使用的是anaconda3。查了很多帖子都不能解決。 參考: https://blog.csdn.net/weixin_43142260/article/details/115198097 https://blog.csdn.net/wenjun_xiao/a ...
  • 線程基礎01 1.程式 進程 線程 程式(program):是為完成的特定任務,用某種語言編寫的一組指令的集合。簡單來說,就是我們寫的代碼。 進程: 進程是指運行中的程式,比如我們使用QQ,就啟動了一個進程,操作系統就會為該進程分配空間。當我們使用迅雷,又啟動了一個進程,操作系統將為迅雷分配新的記憶體 ...
  • 1、請求處理參數 1.1 請求參數 @RequestParam 1.1.1 不使用 @RequestParam 註解 請求參數處理,不使用參數註解: 1.如果請求參數名和請求處理的形參名一致,springMvc 框架會自動將你的請求參數名對應的參數值,綁定到請求方法的形參中,方法內就可以直接使用,不 ...
  • 在大多情況下,我們都是用new去實例化對象。但是,有時候有的對象的類別有很多種,又存在著共性,就好比如汽車,有賓士,紅旗,寶馬等品牌,如果是一個一個去創建類,那就需要創建很多,因此就需要用到工廠模式。 ...
  • 序列類型 str 字元型 list 列表 tuple 元組 列表與元組最大的區別就是列表可變,而元組不可變 遍歷 從頭到尾,依次訪問到每一個 range() 包頭不包尾 range(start,end,step) start 開始 end 結束 step 步長(步長可以是負數) 下標(索引) 序列類 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...