主頁 > 軟體工程 > 2022 大佬帶你飛 Java執行緒池原始碼決議

2022 大佬帶你飛 Java執行緒池原始碼決議

2022-04-29 08:26:50 軟體工程

尊重原創著作權: https://www.gewuweb.com/hot/10338.html

「超詳細」Java執行緒池原始碼決議

尊重原創著作權: https://www.gewuweb.com/sitemap.html

繞不開的執行緒池

只看ThreadPoolExecutor的英文語意就能知道這是一個與執行緒池有關的類,

執行緒池是一種池化技術,Java中類似的池化技術有很多,
常見的有:

  • 資料庫連接池
  • redis連接池
  • http連接池
  • 記憶體池
  • 執行緒池

池化技術的作用:把一些能夠復用的東西(比如說連接、執行緒)放到初始化好的池中,便于資源統一管理,
這樣做的好處:

避免重復創建、銷毀、調度的開銷,提高性能 保證內核的充分利用,防止過分調度 自定義引數配置達到最佳的使用效果

ThreadPoolExecutor 知識點

「超詳細」Java執行緒池原始碼決議

Java中創建執行緒池的方法

不推薦

通過Executors類的靜態方法創建如下執行緒池

  • FixedThreadPool (固定個數)
  • ScheduledThreadPool (執行周期性任務)
  • WorkStealingPool (根據當前電腦CPU處理器數量生成相應執行緒數)
  • CachedThreadPool (帶快取功能)
  • SingleThreadPool (單個執行緒)

「超詳細」Java執行緒池原始碼決議

推薦

通過ThreadPoolExecutor創建執行緒池

    // 給執行緒定義有業務含義的名稱
    ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-%s").build();
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            5,  // 執行緒池核心執行緒數
            10,  // 執行緒池最大執行緒數,達到最大值后執行緒池不會再增加執行緒
            1000,  // 執行緒池中超過corePoolSize數目的空閑執行緒最大存活時間
            TimeUnit.MILLISECONDS,  // 時間單位,毫秒
            new LinkedBlockingQueue<>(50),  // 作業執行緒等待佇列
            threadFactory,  // 自定義執行緒工廠
            new ThreadPoolExecutor.AbortPolicy());  // 執行緒池滿時的拒絕策略
復制代碼

為什么

先來看看阿里巴巴出品的《Java開發手冊》中怎么說?

「超詳細」Java執行緒池原始碼決議

再來看看原始碼怎么寫?

「超詳細」Java執行緒池原始碼決議

以SingleThreadPool為例,其實作也是通過ThreadPoolExecutor的構造方法創建的執行緒池,
之所以不推薦的原因是其使用了LinkedBlockingQueue作為作業執行緒的等待佇列,其是一種無界緩沖等待佇列,該佇列的默認構造器定義的長度為Integer.MAX_VALUE

「超詳細」Java執行緒池原始碼決議

FixedThreadPool同理

「超詳細」Java執行緒池原始碼決議

CachedThreadPool采用了SynchronousQueue佇列,也是一種無界無緩沖等待佇列,而且其最大執行緒數是Integer.MAX_VALUE

「超詳細」Java執行緒池原始碼決議

ScheduledThreadPool采用了DelayedWorkQueue佇列,是一種無界阻塞佇列,其最大執行緒數是Integer.MAX_VALUE

「超詳細」Java執行緒池原始碼決議

以上四種執行緒池都有OOM的風險
相反,在使用ThreadPoolExecutor時,我們可以指定有界/無界阻塞佇列,并指定初始長度,

ThreadPoolExecutor原始碼分析

執行緒池生命周期

「超詳細」Java執行緒池原始碼決議

執行緒池狀態

|

狀態釋義

---|---

RUNNING

|

執行緒池被創建后的初始狀態,能接受新提交的任務,并且也能處理阻塞佇列中的任務

SHUTDOWN

|

關閉狀態,不再接受新提交的任務,但仍可以繼續處理已進入阻塞佇列中的任務

STOP

|

會中斷正在處理任務的執行緒,不能再接受新任務,也不繼續處理佇列中的任務

TIDYING

|

所有的任務都已終止,workerCount(有效作業執行緒數)為0

TERMINATED

|

執行緒池徹底終止運行

Tips:千萬不要把執行緒池的狀態和執行緒的狀態弄混了,補一張網上的執行緒狀態圖

「超詳細」Java執行緒池原始碼決議

Tips:當執行緒呼叫start(),執行緒在JVM中不一定立即執行,有可能要等待作業系統分配資源,此時為READY狀態,當執行緒獲得資源時進入RUNNING狀態,才會真正開始執行,

拒絕策略

  • CallerRunsPolicy(在當前執行緒中執行)

「超詳細」Java執行緒池原始碼決議

  • AbortPolicy(直接拋出RejectedExecutionException)

「超詳細」Java執行緒池原始碼決議

  • DiscardPolicy(直接丟棄執行緒)

「超詳細」Java執行緒池原始碼決議

  • DiscardOldestPolicy(丟棄一個未被處理的最久的執行緒,然后重試)

「超詳細」Java執行緒池原始碼決議

當沒有顯示指明拒絕策略時,默認使用AbortPolicy

「超詳細」Java執行緒池原始碼決議

「超詳細」Java執行緒池原始碼決議

ThreadPoolExecutor類圖

通過IDEA的Diagrams工具查看UML類圖,繼承關系一目了然

「超詳細」Java執行緒池原始碼決議

ThreadPoolExecutor類中的方法很多,最核心就是構造執行緒池的方法和執行執行緒任務的方法,先前已經給出了標準的構造方法,接下來就講一講如何執行執行緒任務...

任務執行機制

  • 通過執行execute方法

該方法無回傳值,為ThreadPoolExecutor自帶方法,傳入Runnable型別物件

「超詳細」Java執行緒池原始碼決議

  • 通過執行submit方法

該方法回傳值為Future物件,為抽象類AbstractExecutorService的方法,被ThreadPoolExecutor繼承,其內部實作也是呼叫了介面類Executor的execute方法,通過上面的類圖可以看到,該方法的實作依然是ThreadPoolExecutor的execute方法

「超詳細」Java執行緒池原始碼決議

「超詳細」Java執行緒池原始碼決議

execute()執行流程圖

「超詳細」Java執行緒池原始碼決議

execute()原始碼解讀

    // 使用原子操作類AtomicInteger的ctl變數,前3位記錄執行緒池的狀態,后29位記錄執行緒數
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    // Integer的范圍為[-2^31,2^31 -1], Integer.SIZE-3 =32-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; // 所有任務都執行完成,且作業執行緒數為0,將呼叫terminated方法
    private static final int TERMINATED =  3 << COUNT_BITS; // 最終狀態,為執行terminated()方法后的狀態

    // ctl變數的封箱拆箱相關的方法
    private static int runStateOf(int c)     { return c & ~CAPACITY; } // 獲取執行緒池運行狀態
    private static int workerCountOf(int c)  { return c & CAPACITY; } // 獲取執行緒池運行執行緒數
    private static int ctlOf(int rs, int wc) { return rs | wc; } // 獲取ctl物件
復制代碼


public void execute(Runnable command) {
    if (command == null) // 任務為空,拋出NPE
        throw new NullPointerException();
        
    int c = ctl.get(); // 獲取當前作業執行緒數和執行緒池運行狀態(共32位,前3位為運行狀態,后29位為運行執行緒數)
    if (workerCountOf(c) < corePoolSize) { // 如果當前作業執行緒數小于核心執行緒數
        if (addWorker(command, true)) // 在addWorker中創建作業執行緒并執行任務
            return;
        c = ctl.get();
    }
    
    // 核心執行緒數已滿(作業執行緒數>核心執行緒數)才會走下面的邏輯
    if (isRunning(c) && workQueue.offer(command)) { // 如果當前執行緒池狀態為RUNNING,并且任務成功添加到阻塞佇列
        int recheck = ctl.get(); // 雙重檢查,因為從上次檢查到進入此方法,執行緒池可能已成為SHUTDOWN狀態
        if (! isRunning(recheck) && remove(command)) // 如果當前執行緒池狀態不是RUNNING則從佇列洗掉任務
            reject(command); // 執行拒絕策略
        else if (workerCountOf(recheck) == 0) // 當執行緒池中的workerCount為0時,此時workQueue中還有待執行的任務,則新增一個addWorker,消費workqueue中的任務
            addWorker(null, false);
    }
    // 阻塞佇列已滿才會走下面的邏輯
    else if (!addWorker(command, false)) // 嘗試增加作業執行緒執行command
        // 如果當前執行緒池為SHUTDOWN狀態或者執行緒池已飽和
        reject(command); // 執行拒絕策略
}
復制代碼


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()) // 換成更直觀的條件陳述句
            // (rs != SHUTDOWN || firstTask != null || workQueue.isEmpty())
           )
           // 回傳false的條件就可以分解為:
           //(1)執行緒池狀態為STOP,TIDYING,TERMINATED
           //(2)執行緒池狀態為SHUTDOWN,且要執行的任務不為空
           //(3)執行緒池狀態為SHUTDOWN,且任務佇列為空
            return false;

        // cas自旋增加執行緒個數
        for (;;) {
            int wc = workerCountOf(c); // 當前作業執行緒數
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize)) // 作業執行緒數>=執行緒池容量 || 作業執行緒數>=(核心執行緒數||最大執行緒數)
                return false;
            if (compareAndIncrementWorkerCount(c)) // 執行cas操作,添加執行緒個數
                break retry; // 添加成功,退出外層回圈
            // 通過cas添加失敗
            c = ctl.get();  
            // 執行緒池狀態是否變化,變化則跳到外層回圈重試重新獲取執行緒池狀態,否者內層回圈重新cas
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    // 簡單總結上面的CAS程序:
    //(1)內層回圈作用是使用cas增加執行緒個數,如果執行緒個數超限則回傳false,否者進行cas
    //(2)cas成功則退出雙回圈,否者cas失敗了,要看當前執行緒池的狀態是否變化了
    //(3)如果變了,則重新進入外層回圈重新獲取執行緒池狀態,否者重新進入內層回圈繼續進行cas

    // 走到這里說明cas成功,執行緒數+1,但并未被執行
    boolean workerStarted = false; // 作業執行緒呼叫start()方法標志
    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()); // 獲取當前執行緒池狀態

                // 執行緒池狀態為RUNNING或者(執行緒池狀態為SHUTDOWN并且沒有新任務時)
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // 檢查執行緒是否處于活躍狀態
                        throw new IllegalThreadStateException();
                    workers.add(w); // 執行緒加入到存放作業執行緒的HashSet容器,workers全域唯一并被mainLock持有
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock(); // finally塊中釋放鎖
            }
            if (workerAdded) { // 執行緒添加成功
                t.start(); // 呼叫執行緒的start()方法
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted) // 如果執行緒啟動失敗,則執行addWorkerFailed方法
            addWorkerFailed(w);
    }
    return workerStarted;
}
復制代碼


private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (w != null)
            workers.remove(w); // 執行緒啟動失敗時,需將前面添加的執行緒洗掉
        decrementWorkerCount(); // ctl變數中的作業執行緒數-1
        tryTerminate(); // 嘗試將執行緒池轉變成TERMINATE狀態
    } finally {
        mainLock.unlock();
    }
}
復制代碼


final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        // 以下情況不會進入TERMINATED狀態:
        //(1)當前執行緒池為RUNNING狀態
        //(2)在TIDYING及以上狀態
        //(3)SHUTDOWN狀態并且作業佇列不為空
        //(4)當前活躍執行緒數不等于0
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        if (workerCountOf(c) != 0) { // 作業執行緒數!=0
            interruptIdleWorkers(ONLY_ONE); // 中斷一個正在等待任務的執行緒
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 通過CAS自旋判斷直到當前執行緒池運行狀態為TIDYING并且活躍執行緒數為0
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated(); // 呼叫執行緒terminated()
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0)); // 設定執行緒池狀態為TERMINATED,作業執行緒數為0
                    termination.signalAll(); // 通過呼叫Condition介面的signalAll()喚醒所有等待的執行緒
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}
復制代碼

Worker原始碼解讀

Worker是ThreadPoolExecutor類的內部類,此處只講最重要的建構式和run方法

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
{
    // 該worker正在運行的執行緒
    final Thread thread;
    
    // 將要運行的初始任務
    Runnable firstTask;
    
    // 每個執行緒的任務計數器
    volatile long completedTasks;

    // 構造方法   
    Worker(Runnable firstTask) {
        setState(-1); // 呼叫runWorker()前禁止中斷
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this); // 通過ThreadFactory創建一個執行緒
    }

    // 實作了Runnable介面的run方法
    public void run() {
        runWorker(this);
    }
    
    ... // 此處省略了其他方法
}
復制代碼

Worker實作了Runable介面,在呼叫start()方法后,實際執行的是run方法

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask; // 獲取作業執行緒中用來執行任務的執行緒實體
    w.firstTask = null;
    w.unlock(); // status設定為0,允許中斷
    boolean completedAbruptly = true; // 執行緒意外終止標志
    try {
        // 如果當前任務不為空,則直接執行;否則呼叫getTask()從任務佇列中取出一個任務執行
        while (task != null || (task = getTask()) != null) {
            w.lock(); // 加鎖,保證下方臨界區代碼的執行緒安全
            // 如果狀態值大于等于STOP且當前執行緒還沒有被中斷,則主動中斷執行緒
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt(); // 中斷當前執行緒
            try {
                beforeExecute(wt, task); // 任務執行前的回呼,空實作,可以在子類中自定義
                Throwable thrown = null;
                try {
                    task.run(); // 執行執行緒的run方法
                } 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,表示已處理完成
                w.completedTasks++; // 當前已完成的任務數+1
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}
復制代碼

從任務佇列中取出一個任務

private Runnable getTask() {
    boolean timedOut = false; // 通過timeOut變數表示執行緒是否空閑時間超時了
    // 無限回圈
    for (;;) {
        int c = ctl.get(); // 執行緒池資訊
        int rs = runStateOf(c); // 執行緒池當前狀態

        // 如果執行緒池狀態>=SHUTDOWN并且作業佇列為空 或 執行緒池狀態>=STOP,則回傳null,讓當前worker被銷毀
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount(); // 作業執行緒數-1
            return null;
        }

        int wc = workerCountOf(c); // 獲取當前執行緒池的作業執行緒數

        // 當前執行緒是否允許超時銷毀的標志
        // 允許超時銷毀:當執行緒池允許核心執行緒超時 或 作業執行緒數>核心執行緒數
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 如果(當前執行緒數大于最大執行緒數 或 (允許超時銷毀 且 當前發生了空閑時間超時))
        // 且(當前執行緒數大于1 或 阻塞佇列為空)
        // 則減少worker計數并回傳null
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 根據執行緒是否允許超時判斷用poll還是take(會阻塞)方法從任務佇列頭部取出一個任務
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r; // 回傳從佇列中取出的任務
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}
復制代碼

總結一下哪些情況getTask()會回傳null:

執行緒池狀態為SHUTDOWN且任務佇列為空 執行緒池狀態為STOP、TIDYING、TERMINATED 執行緒池執行緒數大于最大執行緒數
執行緒可以被超時回收的情況下等待新任務超時

作業執行緒退出

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // 如果completedAbruptly為true則表示任務執行程序中拋出了未處理的例外
    // 所以還沒有正確地減少worker計數,這里需要減少一次worker計數
    if (completedAbruptly) 
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 把將被銷毀的執行緒已完成的任務數累加到執行緒池的完成任務總數上
        completedTaskCount += w.completedTasks;
        workers.remove(w); // 從作業執行緒集合中移除該作業執行緒
    } finally {
        mainLock.unlock();
    }

    // 嘗試結束執行緒池
    tryTerminate();

    int c = ctl.get();
    // 如果是RUNNING 或 SHUTDOWN狀態
    if (runStateLessThan(c, STOP)) {
        // worker是正常執行完
        if (!completedAbruptly) {
            // 如果允許核心執行緒超時則最小執行緒數是0,否則最小執行緒數等于核心執行緒數
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            // 如果阻塞佇列非空,則至少要有一個執行緒繼續執行剩下的任務
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            // 如果當前執行緒數已經滿足最小執行緒數要求,則不需要再創建替代執行緒
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        // 重新創建一個worker來代替被銷毀的執行緒
        addWorker(null, false);
    }
}

Worker(null, false);
}
}

尊重原創著作權: https://www.gewuweb.com/sitemap.html

尊重原創著作權: https://www.gewuweb.com/hot/13313.html

「超詳細」Java執行緒池原始碼決議

繞不開的執行緒池

只看ThreadPoolExecutor的英文語意就能知道這是一個與執行緒池有關的類,

執行緒池是一種池化技術,Java中類似的池化技術有很多,
常見的有:

  • 資料庫連接池
  • redis連接池
  • http連接池
  • 記憶體池
  • 執行緒池

池化技術的作用:把一些能夠復用的東西(比如說連接、執行緒)放到初始化好的池中,便于資源統一管理,
這樣做的好處:

避免重復創建、銷毀、調度的開銷,提高性能 保證內核的充分利用,防止過分調度 自定義引數配置達到最佳的使用效果

ThreadPoolExecutor 知識點

「超詳細」Java執行緒池原始碼決議

Java中創建執行緒池的方法

不推薦

通過Executors類的靜態方法創建如下執行緒池

  • FixedThreadPool (固定個數)
  • ScheduledThreadPool (執行周期性任務)
  • WorkStealingPool (根據當前電腦CPU處理器數量生成相應執行緒數)
  • CachedThreadPool (帶快取功能)
  • SingleThreadPool (單個執行緒)

「超詳細」Java執行緒池原始碼決議

推薦

通過ThreadPoolExecutor創建執行緒池

    // 給執行緒定義有業務含義的名稱
    ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-%s").build();
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            5,  // 執行緒池核心執行緒數
            10,  // 執行緒池最大執行緒數,達到最大值后執行緒池不會再增加執行緒
            1000,  // 執行緒池中超過corePoolSize數目的空閑執行緒最大存活時間
            TimeUnit.MILLISECONDS,  // 時間單位,毫秒
            new LinkedBlockingQueue<>(50),  // 作業執行緒等待佇列
            threadFactory,  // 自定義執行緒工廠
            new ThreadPoolExecutor.AbortPolicy());  // 執行緒池滿時的拒絕策略
復制代碼

為什么

先來看看阿里巴巴出品的《Java開發手冊》中怎么說?

「超詳細」Java執行緒池原始碼決議

再來看看原始碼怎么寫?

「超詳細」Java執行緒池原始碼決議

以SingleThreadPool為例,其實作也是通過ThreadPoolExecutor的構造方法創建的執行緒池,
之所以不推薦的原因是其使用了LinkedBlockingQueue作為作業執行緒的等待佇列,其是一種無界緩沖等待佇列,該佇列的默認構造器定義的長度為Integer.MAX_VALUE

「超詳細」Java執行緒池原始碼決議

FixedThreadPool同理

「超詳細」Java執行緒池原始碼決議

CachedThreadPool采用了SynchronousQueue佇列,也是一種無界無緩沖等待佇列,而且其最大執行緒數是Integer.MAX_VALUE

「超詳細」Java執行緒池原始碼決議

ScheduledThreadPool采用了DelayedWorkQueue佇列,是一種無界阻塞佇列,其最大執行緒數是Integer.MAX_VALUE

「超詳細」Java執行緒池原始碼決議

以上四種執行緒池都有OOM的風險
相反,在使用ThreadPoolExecutor時,我們可以指定有界/無界阻塞佇列,并指定初始長度,

ThreadPoolExecutor原始碼分析

執行緒池生命周期

「超詳細」Java執行緒池原始碼決議

執行緒池狀態

|

狀態釋義

---|---

RUNNING

|

執行緒池被創建后的初始狀態,能接受新提交的任務,并且也能處理阻塞佇列中的任務

SHUTDOWN

|

關閉狀態,不再接受新提交的任務,但仍可以繼續處理已進入阻塞佇列中的任務

STOP

|

會中斷正在處理任務的執行緒,不能再接受新任務,也不繼續處理佇列中的任務

TIDYING

|

所有的任務都已終止,workerCount(有效作業執行緒數)為0

TERMINATED

|

執行緒池徹底終止運行

Tips:千萬不要把執行緒池的狀態和執行緒的狀態弄混了,補一張網上的執行緒狀態圖

「超詳細」Java執行緒池原始碼決議

Tips:當執行緒呼叫start(),執行緒在JVM中不一定立即執行,有可能要等待作業系統分配資源,此時為READY狀態,當執行緒獲得資源時進入RUNNING狀態,才會真正開始執行,

拒絕策略

  • CallerRunsPolicy(在當前執行緒中執行)

「超詳細」Java執行緒池原始碼決議

  • AbortPolicy(直接拋出RejectedExecutionException)

「超詳細」Java執行緒池原始碼決議

  • DiscardPolicy(直接丟棄執行緒)

「超詳細」Java執行緒池原始碼決議

  • DiscardOldestPolicy(丟棄一個未被處理的最久的執行緒,然后重試)

「超詳細」Java執行緒池原始碼決議

當沒有顯示指明拒絕策略時,默認使用AbortPolicy

「超詳細」Java執行緒池原始碼決議

「超詳細」Java執行緒池原始碼決議

ThreadPoolExecutor類圖

通過IDEA的Diagrams工具查看UML類圖,繼承關系一目了然

「超詳細」Java執行緒池原始碼決議

ThreadPoolExecutor類中的方法很多,最核心就是構造執行緒池的方法和執行執行緒任務的方法,先前已經給出了標準的構造方法,接下來就講一講如何執行執行緒任務...

任務執行機制

  • 通過執行execute方法

該方法無回傳值,為ThreadPoolExecutor自帶方法,傳入Runnable型別物件

「超詳細」Java執行緒池原始碼決議

  • 通過執行submit方法

該方法回傳值為Future物件,為抽象類AbstractExecutorService的方法,被ThreadPoolExecutor繼承,其內部實作也是呼叫了介面類Executor的execute方法,通過上面的類圖可以看到,該方法的實作依然是ThreadPoolExecutor的execute方法

「超詳細」Java執行緒池原始碼決議

「超詳細」Java執行緒池原始碼決議

execute()執行流程圖

「超詳細」Java執行緒池原始碼決議

execute()原始碼解讀

    // 使用原子操作類AtomicInteger的ctl變數,前3位記錄執行緒池的狀態,后29位記錄執行緒數
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    // Integer的范圍為[-2^31,2^31 -1], Integer.SIZE-3 =32-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; // 所有任務都執行完成,且作業執行緒數為0,將呼叫terminated方法
    private static final int TERMINATED =  3 << COUNT_BITS; // 最終狀態,為執行terminated()方法后的狀態

    // ctl變數的封箱拆箱相關的方法
    private static int runStateOf(int c)     { return c & ~CAPACITY; } // 獲取執行緒池運行狀態
    private static int workerCountOf(int c)  { return c & CAPACITY; } // 獲取執行緒池運行執行緒數
    private static int ctlOf(int rs, int wc) { return rs | wc; } // 獲取ctl物件
復制代碼


public void execute(Runnable command) {
    if (command == null) // 任務為空,拋出NPE
        throw new NullPointerException();
        
    int c = ctl.get(); // 獲取當前作業執行緒數和執行緒池運行狀態(共32位,前3位為運行狀態,后29位為運行執行緒數)
    if (workerCountOf(c) < corePoolSize) { // 如果當前作業執行緒數小于核心執行緒數
        if (addWorker(command, true)) // 在addWorker中創建作業執行緒并執行任務
            return;
        c = ctl.get();
    }
    
    // 核心執行緒數已滿(作業執行緒數>核心執行緒數)才會走下面的邏輯
    if (isRunning(c) && workQueue.offer(command)) { // 如果當前執行緒池狀態為RUNNING,并且任務成功添加到阻塞佇列
        int recheck = ctl.get(); // 雙重檢查,因為從上次檢查到進入此方法,執行緒池可能已成為SHUTDOWN狀態
        if (! isRunning(recheck) && remove(command)) // 如果當前執行緒池狀態不是RUNNING則從佇列洗掉任務
            reject(command); // 執行拒絕策略
        else if (workerCountOf(recheck) == 0) // 當執行緒池中的workerCount為0時,此時workQueue中還有待執行的任務,則新增一個addWorker,消費workqueue中的任務
            addWorker(null, false);
    }
    // 阻塞佇列已滿才會走下面的邏輯
    else if (!addWorker(command, false)) // 嘗試增加作業執行緒執行command
        // 如果當前執行緒池為SHUTDOWN狀態或者執行緒池已飽和
        reject(command); // 執行拒絕策略
}
復制代碼


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()) // 換成更直觀的條件陳述句
            // (rs != SHUTDOWN || firstTask != null || workQueue.isEmpty())
           )
           // 回傳false的條件就可以分解為:
           //(1)執行緒池狀態為STOP,TIDYING,TERMINATED
           //(2)執行緒池狀態為SHUTDOWN,且要執行的任務不為空
           //(3)執行緒池狀態為SHUTDOWN,且任務佇列為空
            return false;

        // cas自旋增加執行緒個數
        for (;;) {
            int wc = workerCountOf(c); // 當前作業執行緒數
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize)) // 作業執行緒數>=執行緒池容量 || 作業執行緒數>=(核心執行緒數||最大執行緒數)
                return false;
            if (compareAndIncrementWorkerCount(c)) // 執行cas操作,添加執行緒個數
                break retry; // 添加成功,退出外層回圈
            // 通過cas添加失敗
            c = ctl.get();  
            // 執行緒池狀態是否變化,變化則跳到外層回圈重試重新獲取執行緒池狀態,否者內層回圈重新cas
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    // 簡單總結上面的CAS程序:
    //(1)內層回圈作用是使用cas增加執行緒個數,如果執行緒個數超限則回傳false,否者進行cas
    //(2)cas成功則退出雙回圈,否者cas失敗了,要看當前執行緒池的狀態是否變化了
    //(3)如果變了,則重新進入外層回圈重新獲取執行緒池狀態,否者重新進入內層回圈繼續進行cas

    // 走到這里說明cas成功,執行緒數+1,但并未被執行
    boolean workerStarted = false; // 作業執行緒呼叫start()方法標志
    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()); // 獲取當前執行緒池狀態

                // 執行緒池狀態為RUNNING或者(執行緒池狀態為SHUTDOWN并且沒有新任務時)
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // 檢查執行緒是否處于活躍狀態
                        throw new IllegalThreadStateException();
                    workers.add(w); // 執行緒加入到存放作業執行緒的HashSet容器,workers全域唯一并被mainLock持有
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock(); // finally塊中釋放鎖
            }
            if (workerAdded) { // 執行緒添加成功
                t.start(); // 呼叫執行緒的start()方法
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted) // 如果執行緒啟動失敗,則執行addWorkerFailed方法
            addWorkerFailed(w);
    }
    return workerStarted;
}
復制代碼


private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (w != null)
            workers.remove(w); // 執行緒啟動失敗時,需將前面添加的執行緒洗掉
        decrementWorkerCount(); // ctl變數中的作業執行緒數-1
        tryTerminate(); // 嘗試將執行緒池轉變成TERMINATE狀態
    } finally {
        mainLock.unlock();
    }
}
復制代碼


final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        // 以下情況不會進入TERMINATED狀態:
        //(1)當前執行緒池為RUNNING狀態
        //(2)在TIDYING及以上狀態
        //(3)SHUTDOWN狀態并且作業佇列不為空
        //(4)當前活躍執行緒數不等于0
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        if (workerCountOf(c) != 0) { // 作業執行緒數!=0
            interruptIdleWorkers(ONLY_ONE); // 中斷一個正在等待任務的執行緒
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 通過CAS自旋判斷直到當前執行緒池運行狀態為TIDYING并且活躍執行緒數為0
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated(); // 呼叫執行緒terminated()
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0)); // 設定執行緒池狀態為TERMINATED,作業執行緒數為0
                    termination.signalAll(); // 通過呼叫Condition介面的signalAll()喚醒所有等待的執行緒
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}
復制代碼

Worker原始碼解讀

Worker是ThreadPoolExecutor類的內部類,此處只講最重要的建構式和run方法

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
{
    // 該worker正在運行的執行緒
    final Thread thread;
    
    // 將要運行的初始任務
    Runnable firstTask;
    
    // 每個執行緒的任務計數器
    volatile long completedTasks;

    // 構造方法   
    Worker(Runnable firstTask) {
        setState(-1); // 呼叫runWorker()前禁止中斷
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this); // 通過ThreadFactory創建一個執行緒
    }

    // 實作了Runnable介面的run方法
    public void run() {
        runWorker(this);
    }
    
    ... // 此處省略了其他方法
}
復制代碼

Worker實作了Runable介面,在呼叫start()方法后,實際執行的是run方法

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask; // 獲取作業執行緒中用來執行任務的執行緒實體
    w.firstTask = null;
    w.unlock(); // status設定為0,允許中斷
    boolean completedAbruptly = true; // 執行緒意外終止標志
    try {
        // 如果當前任務不為空,則直接執行;否則呼叫getTask()從任務佇列中取出一個任務執行
        while (task != null || (task = getTask()) != null) {
            w.lock(); // 加鎖,保證下方臨界區代碼的執行緒安全
            // 如果狀態值大于等于STOP且當前執行緒還沒有被中斷,則主動中斷執行緒
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt(); // 中斷當前執行緒
            try {
                beforeExecute(wt, task); // 任務執行前的回呼,空實作,可以在子類中自定義
                Throwable thrown = null;
                try {
                    task.run(); // 執行執行緒的run方法
                } 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,表示已處理完成
                w.completedTasks++; // 當前已完成的任務數+1
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}
復制代碼

從任務佇列中取出一個任務

private Runnable getTask() {
    boolean timedOut = false; // 通過timeOut變數表示執行緒是否空閑時間超時了
    // 無限回圈
    for (;;) {
        int c = ctl.get(); // 執行緒池資訊
        int rs = runStateOf(c); // 執行緒池當前狀態

        // 如果執行緒池狀態>=SHUTDOWN并且作業佇列為空 或 執行緒池狀態>=STOP,則回傳null,讓當前worker被銷毀
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount(); // 作業執行緒數-1
            return null;
        }

        int wc = workerCountOf(c); // 獲取當前執行緒池的作業執行緒數

        // 當前執行緒是否允許超時銷毀的標志
        // 允許超時銷毀:當執行緒池允許核心執行緒超時 或 作業執行緒數>核心執行緒數
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 如果(當前執行緒數大于最大執行緒數 或 (允許超時銷毀 且 當前發生了空閑時間超時))
        // 且(當前執行緒數大于1 或 阻塞佇列為空)
        // 則減少worker計數并回傳null
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 根據執行緒是否允許超時判斷用poll還是take(會阻塞)方法從任務佇列頭部取出一個任務
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r; // 回傳從佇列中取出的任務
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}
復制代碼

總結一下哪些情況getTask()會回傳null:

執行緒池狀態為SHUTDOWN且任務佇列為空 執行緒池狀態為STOP、TIDYING、TERMINATED 執行緒池執行緒數大于最大執行緒數
執行緒可以被超時回收的情況下等待新任務超時

作業執行緒退出

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // 如果completedAbruptly為true則表示任務執行程序中拋出了未處理的例外
    // 所以還沒有正確地減少worker計數,這里需要減少一次worker計數
    if (completedAbruptly) 
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 把將被銷毀的執行緒已完成的任務數累加到執行緒池的完成任務總數上
        completedTaskCount += w.completedTasks;
        workers.remove(w); // 從作業執行緒集合中移除該作業執行緒
    } finally {
        mainLock.unlock();
    }

    // 嘗試結束執行緒池
    tryTerminate();

    int c = ctl.get();
    // 如果是RUNNING 或 SHUTDOWN狀態
    if (runStateLessThan(c, STOP)) {
        // worker是正常執行完
        if (!completedAbruptly) {
            // 如果允許核心執行緒超時則最小執行緒數是0,否則最小執行緒數等于核心執行緒數
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            // 如果阻塞佇列非空,則至少要有一個執行緒繼續執行剩下的任務
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            // 如果當前執行緒數已經滿足最小執行緒數要求,則不需要再創建替代執行緒
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        // 重新創建一個worker來代替被銷毀的執行緒
        addWorker(null, false);
    }
}

Worker(null, false);
}
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/467085.html

標籤:其他

上一篇:2022年了 Python爬蟲能當副業嗎?封鎖在家能掙錢的方式

下一篇:介面管理工具YApi怎么用?顏值高、易管理、超好用

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • Git本地庫既關聯GitHub又關聯Gitee

    創建代碼倉庫 使用gitee舉例(github和gitee差不多) 1.在gitee右上角點擊+,選擇新建倉庫 ? 2.選擇填寫倉庫資訊,然后進行創建 ? 3.服務端已經準備好了,本地開始作準備 (1)Git 全域設定 git config --global user.name "成鈺" git c ......

    uj5u.com 2020-09-10 05:04:14 more
  • CODING DevOps 代碼質量實戰系列第二課,相約周三

    隨著 ToB(企業服務)的興起和 ToC(消費互聯網)產品進入成熟期,線上故障帶來的損失越來越大,代碼質量越來越重要,而「質量內建」正是 DevOps 核心理念之一。**《DevOps 代碼質量實戰(PHP 版)》**為 CODING DevOps 代碼質量實戰系列的第二課,同時也是本系列的 PHP ......

    uj5u.com 2020-09-10 05:07:43 more
  • 推薦Scrum書籍

    推薦Scrum書籍 直接上干貨,推薦書籍清單如下(推薦有順序的哦) Scrum指南 Scrum精髓 Scrum敏捷軟體開發 Scrum捷徑 硝煙中的Scrum和XP : 我們如何實施Scrum 敏捷軟體開發:Scrum實戰指南 Scrum要素 大規模Scrum:大規模敏捷組織的設計 用戶故事地圖 用 ......

    uj5u.com 2020-09-10 05:07:45 more
  • CODING DevOps 代碼質量實戰系列最后一課,周四發車

    隨著 ToB(企業服務)的興起和 ToC(消費互聯網)產品進入成熟期,線上故障帶來的損失越來越大,代碼質量越來越重要,而「質量內建」正是 DevOps 核心理念之一。 **《DevOps 代碼質量實戰(Java 版)》**為 CODING DevOps 代碼質量實戰系列的最后一課,同時也是本系列的 ......

    uj5u.com 2020-09-10 05:07:52 more
  • 敏捷軟體工程實踐書籍

    Scrum轉型想要做好,第一步先了解并真正落實Scrum,那么我推薦的Scrum書籍是要看懂并實踐的。第二步是團隊的工程實踐要做扎實。 下面推薦工程實踐書單: 重構:改善既有代碼的設計 決議極限編程 : 擁抱變化 代碼整潔代碼 程式員的職業素養 修改代碼的藝術 撰寫可讀代碼的藝術 測驗驅動開發 : ......

    uj5u.com 2020-09-10 05:07:55 more
  • Jenkins+svn+nginx實作windows環境自動部署vue前端專案

    前面文章介紹了Jenkins+svn+tomcat實作自動化部署,現在終于有空抽時間出來寫下Jenkins+svn+nginx實作自動部署vue前端專案。 jenkins的安裝和配置已經在前面文章進行介紹,下面介紹實作vue前端專案需要進行的哪些額外的步驟。 注意:在安裝jenkins和nginx的 ......

    uj5u.com 2020-09-10 05:08:49 more
  • CODING DevOps 微服務專案實戰系列第一課,明天等你

    CODING DevOps 微服務專案實戰系列第一課**《DevOps 微服務專案實戰:DevOps 初體驗》**將由 CODING DevOps 開發工程師 王寬老師 向大家介紹 DevOps 的基本理念,并探討為什么現代開發活動需要 DevOps,同時將以 eShopOnContainers 項 ......

    uj5u.com 2020-09-10 05:09:14 more
  • CODING DevOps 微服務專案實戰系列第二課來啦!

    近年來,工程專案的結構越來越復雜,需要接入合適的持續集成流水線形式,才能滿足更多變的需求,那么如何優雅地使用 CI 能力提升生產效率呢?CODING DevOps 微服務專案實戰系列第二課 《DevOps 微服務專案實戰:CI 進階用法》 將由 CODING DevOps 全堆疊工程師 何晨哲老師 向 ......

    uj5u.com 2020-09-10 05:09:33 more
  • CODING DevOps 微服務專案實戰系列最后一課,周四開講!

    隨著軟體工程越來越復雜化,如何在 Kubernetes 集群進行灰度發布成為了生產部署的”必修課“,而如何實作安全可控、自動化的灰度發布也成為了持續部署重點關注的問題。CODING DevOps 微服務專案實戰系列最后一課:**《DevOps 微服務專案實戰:基于 Nginx-ingress 的自動 ......

    uj5u.com 2020-09-10 05:10:00 more
  • CODING 儀表盤功能正式推出,實作作業資料可視化!

    CODING 儀表盤功能現已正式推出!該功能旨在用一張張統計卡片的形式,統計并展示使用 CODING 中所產生的資料。這意味著無需額外的設定,就可以收集歸納寶貴的作業資料并予之量化分析。這些海量的資料皆會以圖表或串列的方式躍然紙上,方便團隊成員隨時查看各專案的進度、狀態和指標,云端協作迎來真正意義上 ......

    uj5u.com 2020-09-10 05:11:01 more
最新发布
  • windows系統git使用ssh方式和gitee/github進行同步

    使用git來clone專案有兩種方式:HTTPS和SSH:
    HTTPS:不管是誰,拿到url隨便clone,但是在push的時候需要驗證用戶名和密碼;
    SSH:clone的專案你必須是擁有者或者管理員,而且需要在clone前添加SSH Key。SSH 在push的時候,是不需要輸入用戶名的,如果配置... ......

    uj5u.com 2023-04-19 08:41:12 more
  • windows系統git使用ssh方式和gitee/github進行同步

    使用git來clone專案有兩種方式:HTTPS和SSH:
    HTTPS:不管是誰,拿到url隨便clone,但是在push的時候需要驗證用戶名和密碼;
    SSH:clone的專案你必須是擁有者或者管理員,而且需要在clone前添加SSH Key。SSH 在push的時候,是不需要輸入用戶名的,如果配置... ......

    uj5u.com 2023-04-19 08:35:34 more
  • 2023年農牧行業6大CRM系統、5大場景盤點

    在物聯網、大資料、云計算、人工智能、自動化技術等現代資訊技術蓬勃發展與逐步成熟的背景下,數字化正成為農牧行業供給側結構性變革與高質量發展的核心驅動因素。因此,改造和提升傳統農牧業、開拓創新現代智慧農牧業,加快推進農牧業的現代化、資訊化、數字化建設已成為農牧業發展的重要方向。 當下,企業數字化轉型已經 ......

    uj5u.com 2023-04-18 08:05:44 more
  • 2023年農牧行業6大CRM系統、5大場景盤點

    在物聯網、大資料、云計算、人工智能、自動化技術等現代資訊技術蓬勃發展與逐步成熟的背景下,數字化正成為農牧行業供給側結構性變革與高質量發展的核心驅動因素。因此,改造和提升傳統農牧業、開拓創新現代智慧農牧業,加快推進農牧業的現代化、資訊化、數字化建設已成為農牧業發展的重要方向。 當下,企業數字化轉型已經 ......

    uj5u.com 2023-04-18 08:00:18 more
  • 計算機組成原理—存盤器

    計算機組成原理—硬體結構 二、存盤器 1.概述 存盤器是計算機系統中的記憶設備,用來存放程式和資料 1.1存盤器的層次結構 快取-主存層次主要解決CPU和主存速度不匹配的問題,速度接近快取 主存-輔存層次主要解決存盤系統的容量問題,容量接近與價位接近于主存 2.主存盤器 2.1概述 主存與CPU的聯 ......

    uj5u.com 2023-04-17 08:20:31 more
  • 談一談我對協同開發的一些認識

    如今各互聯網公司普通都使用敏捷開發,采用小步快跑的形式來進行專案開發。如果是小專案或者小需求,那一個開發可能就搞定了。但對于電商等復雜的系統,其功能多,結構復雜,一個人肯定是搞不定的,所以都是很多人來共同開發維護。以我曾經待過的商城團隊為例,光是后端開發就有七十多人。 為了更好地開發這類大型系統,往 ......

    uj5u.com 2023-04-17 08:18:55 more
  • 專案管理PRINCE2核心知識點整理

    PRINCE2,即 PRoject IN Controlled Environment(受控環境中的專案)是一種結構化的專案管理方法論,由英國政府內閣商務部(OGC)推出,是英國專案管理標準。
    PRINCE2 作為一種開放的方法論,是一套結構化的專案管理流程,描述了如何以一種邏輯性的、有組織的方法,... ......

    uj5u.com 2023-04-17 08:18:51 more
  • 談一談我對協同開發的一些認識

    如今各互聯網公司普通都使用敏捷開發,采用小步快跑的形式來進行專案開發。如果是小專案或者小需求,那一個開發可能就搞定了。但對于電商等復雜的系統,其功能多,結構復雜,一個人肯定是搞不定的,所以都是很多人來共同開發維護。以我曾經待過的商城團隊為例,光是后端開發就有七十多人。 為了更好地開發這類大型系統,往 ......

    uj5u.com 2023-04-17 08:18:00 more
  • 專案管理PRINCE2核心知識點整理

    PRINCE2,即 PRoject IN Controlled Environment(受控環境中的專案)是一種結構化的專案管理方法論,由英國政府內閣商務部(OGC)推出,是英國專案管理標準。
    PRINCE2 作為一種開放的方法論,是一套結構化的專案管理流程,描述了如何以一種邏輯性的、有組織的方法,... ......

    uj5u.com 2023-04-17 08:17:55 more
  • 計算機組成原理—存盤器

    計算機組成原理—硬體結構 二、存盤器 1.概述 存盤器是計算機系統中的記憶設備,用來存放程式和資料 1.1存盤器的層次結構 快取-主存層次主要解決CPU和主存速度不匹配的問題,速度接近快取 主存-輔存層次主要解決存盤系統的容量問題,容量接近與價位接近于主存 2.主存盤器 2.1概述 主存與CPU的聯 ......

    uj5u.com 2023-04-17 08:12:06 more