深入淺出線程池

来源:https://www.cnblogs.com/Jcloud/archive/2023/09/22/17721770.html
-Advertisement-
Play Games

線程(thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際 運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線 程並行執行不同的任務。 ...


一、線程

1、什麼是線程

線程(thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際 運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線 程並行執行不同的任務。

2、如何創建線程

2.1、JAVA中創建線程

/**
 * 繼承Thread類,重寫run方法
 */
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("myThread..." + Thread.currentThread().getName());
} }

/**
 * 實現Runnable介面,實現run方法 
 */
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("MyRunnable..." + Thread.currentThread().getName());
} }

/**
 * 實現Callable介面,指定返回類型,實現call方法
 */
class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "MyCallable..." + Thread.currentThread().getName();
} }

2.2、測試一下

public static void main(String[] args) throws Exception {
    MyThread thread = new MyThread();
    thread.run();   //myThread...main
    thread.start(); //myThread...Thread-0
    
    MyRunnable myRunnable = new MyRunnable();
    Thread thread1 = new Thread(myRunnable);
    myRunnable.run();   //MyRunnable...main
    thread1.start();    //MyRunnable...Thread-1
    
    MyCallable myCallable = new MyCallable();
    FutureTask<String> futureTask = new FutureTask<>(myCallable);
    Thread thread2 = new Thread(futureTask);
    thread2.start();
    System.out.println(myCallable.call());  //MyCallable...main
    System.out.println(futureTask.get());   //MyCallable...Thread-2

} 

2.3、問題

既然我們創建了線程,那為何我們直接調用方法和我們調用start()方法的結果不同?new Thread() 是否真實創建了線程?

2.4、問題分析

我們直接調用方法,可以看到是執行的主線程,而調用start()方法就是開啟了新線程,那說明new Thread()並沒有創建線程,而是在start()中創建了線程。

那我們看下Thread類start()方法:

class Thread implements Runnable { //Thread類實現了Runnalbe介面,實現了run()方法 
    
    private Runnable target;

    public synchronized void start() {
        ...

        boolean started = false;
        try {
            start0(); //可以看到,start()方法真實的調用時start0()方法 
            started = true;
        } finally {
            ...     
        } 
    }
    
    private native void start0();  //start0()是一個native方法,由JVM調用底層操作系統,開啟一個線程,由操作系統過統一調度 

    @Override
    public void run() {
        if (target != null) {
             target.run(); //操作系統在執行新開啟的線程時,回調Runnable介面的run()方法,執行我們預設的線程任務

        } 
     } 
} 

2.5、總結

  1. JAVA不能直接創建線程執行任務,而是通過創建Thread對象調用操作系統開啟線程,在由操作系 統回調Runnable介面的run()方法執行任務;

  2. 實現Runnable的方式,將線程實際要執行的回調任務單獨提出來了,實現線程的啟動與回調任務 解耦;

  3. 實現Callable的方式,通過Future模式不但將線程的啟動與回調任務解耦,而且可以在執行完成後 獲取到執行的結果;

二、多線程

1、什麼是多線程

多線程(multithreading),是指從軟體或者硬體上實現多個線程併發執行的技術。同一個線程只 能處理完一個任務在處理下一個任務,有時我們需要多個任務同時處理,這時,我們就需要創建多 個線程來同時處理任務。

2、多線程有什麼好處

2.1、串列處理

public static void main(String[] args) throws Exception {
    System.out.println("start...");
    long start = System.currentTimeMillis();
    for (int i = 0; i < 5; i++) {
        Thread.sleep(2000);  //每個任務執行2秒 
        System.out.println("task done..."); //處理執行結果
    }
    long end = System.currentTimeMillis();
    System.out.println("end...,time = "  + (end - start));
}
//執行結果
start...
task done...
task done...
task done...
task done...
task done... end...,time = 10043

2.2、並行處理

public static void main(String[] args) throws Exception {
    System.out.println("start...");
    long start = System.currentTimeMillis();
    List<Future> list = new ArrayList<>();

    for (int i = 0; i < 5; i++) {
        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(2000); //每個任務執行2秒 
                return "task done...";
            }

        };
        FutureTask task = new FutureTask(callable);
        list.add(task);
        new Thread(task).start();

    }
    
    list.forEach(future -> {
        try { 
            System.out.println(future.get()); //處理執行結果 } catch (Exception e) {
         } 
    });
    
    long end = System.currentTimeMillis();
    System.out.println("end...,time = " + (end - start));

} 
//執行結果
 start...
 task done...
 task done...
 task done...
 task done...
 task done... end...,time = 2005 

2.3、總結

  1. 多線程可以把一個任務拆分為幾個子任務,多個子任務可以併發執行,每一個子任務就是一個線程。

  2. 多線程是為了同步完成多項任務,不是為了提高運行效率,而是為了提高資源使用效率來提高系統 的效率。

2.4、多線程的問題

上面示例中我們可以看到,如果每來一個任務,我們就創建一個線程,有很多任務的情況下,我們 會創建大量的線程,可能會導致系統資源的耗盡。同時,我們知道線程的執行是需要搶占CPU資源 的,那如果有太多的線程,就會導致大量時間用線上程切換的開銷上。

再有,每來一個任務都需要創建一個線程,而創建一個線程需要調用操作系統底層方法,開銷較 大,而線程執行完成後就被回收了。在需要大量線程的時候,創建線程的時間就花費不少了。

三、線程池

1、如何設計一個線程池

由於多線程的開發存在上述的一些問題,那我們是否可以設計一個東西來避免這些問題呢?當然可以! 線程池就是為瞭解決這些問題而生的。那我們該如何設計一個線程池來解決這些問題呢?或者說,一個線程池該具備什麼樣的功能?

1.1、線程池基本功能

  1. 多線程會創建大量的線程耗盡資源,那線程池應該對線程數量有所限制,可以保證不會耗盡系統資 源;

  2. 每次創建新的線程會增加創建時的開銷,那線程池應該減少線程的創建,儘量復用已創建好的線 程;

1.2、線程池面臨問題

  1. 我們知道線程在執行完自己的任務後就會被回收,那我們如何復用線程?

  2. 我們指定了線程的最大數量,當任務數超出線程數時,我們該如何處理?

1.3、創新源於生活

先假設一個場景:假設我們是一個物流公司的管理人員,要配送的貨物就是我們的任務,貨車就是 我們配送工具,我們當然不能有多少貨物就準備多少貨車。那當顧客源源不斷的將貨物交給我們配 送,我們該如何管理才能讓公司經營的最好呢?

  1. 最開始貨物來的時候,我們還沒有貨車,每批要運輸的貨物我們都要購買一輛車來運輸;

  2. 當貨車運輸完成後,暫時還沒有下一批貨物到達,那貨車就在倉庫停著,等有貨物來了立馬就可以 運輸;

  3. 當我們有了一定數量的車後,我們認為已經夠用了,那後面就不再買車了,這時要是由新的貨物來 了,我們就會讓貨物先放倉庫,等有車回來在配送;

  4. 當618大促來襲,要配送的貨物太多,車都在路上,倉庫也都放滿了,那怎麼辦呢?我們就選擇臨 時租一些車來幫忙配送,提高配送的效率;

  5. 但是貨物還是太多,我們增加了臨時的貨車,依舊配送不過來,那這時我們就沒辦法了,只能讓發 貨的客戶排隊等候或者乾脆不接受了;

  6. 大促圓滿完成後,累計的貨物已經配送完成了,為了降低成本,我們就將臨時租的車都還了;

1.4、技術源於創新

基於上述場景,物流公司就是我們的線程池、貨物就是我們的線程任務、貨車就是我們的線程。我 們如何設計公司的管理貨車的流程,就應該如何設計線程池管理線程的流程。

  1. 當任務進來我們還沒有線程時,我們就該創建線程執行任務;

  2. 當線程任務執行完成後,線程不釋放,等著下一個任務進來後接著執行;

  3. 當創建的線程數量達到一定量後,新來的任務我們存起來等待空閑線程執行,這就要求線程池有個 存任務的容器;

  4. 當容器存滿後,我們需要增加一些臨時的線程來提高處理效率;

  5. 當增加臨時線程後依舊處理不了的任務,那就應該將此任務拒絕;

  6. 當所有任務執行完成後,就應該將臨時的線程釋放掉,以免增加不必要的開銷;

2、線程池具體分析

上文中,我們講了該如何設計一個線程池,下麵我們看看大神是如何設計的;

2.1、 JAVA中的線程池是如何設計的

2.1.1、 線程池設計

看下線程池中的屬性,瞭解線程池的設計。

public class ThreadPoolExecutor extends AbstractExecutorService {

    //線程池的打包控制狀態,用高3位來表示線程池的運行狀態,低29位來表示線程池中工作線程的數量 
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); 
    
    //值為29,用來表示偏移量
     private static final int COUNT_BITS = Integer.SIZE - 3; 

    //線程池的最大容量
     private static final int CAPACITY = (1 << COUNT_BITS) - 1; 

    //線程池的運行狀態,總共有5個狀態,用高3位來表示 
    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()方法已經執行結束

    //任務緩存隊列,用來存放等待執行的任務
    private final BlockingQueue<Runnable> workQueue; 

    //全局鎖,對線程池狀態等屬性修改時需要使用這個鎖
    private final ReentrantLock mainLock = new ReentrantLock(); 

    //線程池中工作線程的集合,訪問和修改需要持有全局鎖
    private final HashSet<Worker> workers = new HashSet<Worker>(); 

    // 終止條件
    private final Condition termination = mainLock.newCondition(); 

    //線程池中曾經出現過的最大線程數 
    private int largestPoolSize; 
    
    //已完成任務的數量
    private long completedTaskCount; 
    
    //線程工廠
    private volatile ThreadFactory threadFactory; 
    
    //任務拒絕策略
    private volatile RejectedExecutionHandler handler; 

    //線程存活時間
    private volatile long keepAliveTime; 

    //是否允許核心線程超時
    private volatile boolean allowCoreThreadTimeOut; 

    //核心池大小,若allowCoreThreadTimeOut被設置,核心線程全部空閑超時被回收的情況下會為0 
    private volatile int corePoolSize; 

    //最大池大小,不得超過CAPACITY
    private volatile int maximumPoolSize; 
    
    //預設的任務拒絕策略
    private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();

    //運行許可權相關
    private static final RuntimePermission shutdownPerm = 
        new RuntimePermission("modifyThread");

    ... 
} 

小結一下:以上線程池的設計可以看出,線程池的功能還是很完善的。

  1. 提供了線程創建、數量及存活時間等的管理;

  2. 提供了線程池狀態流轉的管理;

  3. 提供了任務緩存的各種容器;

  4. 提供了多餘任務的處理機制;

  5. 提供了簡單的統計功能;

2.1.2、線程池構造函數

//構造函數
 public ThreadPoolExecutor(int corePoolSize, //核心線程數 
                           int maximumPoolSize, //最大允許線程數 
                           long keepAliveTime, //線程存活時間 
                           TimeUnit unit, //存活時間單位 
                           BlockingQueue<Runnable> workQueue, //任務緩存隊列
                           ThreadFactory threadFactory, //線程工廠 
                           RejectedExecutionHandler handler) { //拒絕策略 
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
        
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
        
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
} 

小結一下:

  1. 構造函數告訴了我們可以怎樣去適用線程池,線程池的哪些特性是我們可以控制的;

2.1.3、線程池執行

2.1.3.1、提交任務方法

• public void execute(Runnable command);

• Future<?> submit(Runnable task);

• Future submit(Runnable task, T result);

• Future submit(Callable task);

public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
}

可以看到submit方法的底層調用的也是execute方法,所以我們這裡只分析execute方法;

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        
        int c = ctl.get();
        //第一步:創建核心線程
        if (workerCountOf(c) < corePoolSize) {  //worker數量小於corePoolSize
            if (addWorker(command, true))       //創建worker
                return;
            c = ctl.get();
        }
        //第二步:加入緩存隊列
        if (isRunning(c) && workQueue.offer(command)) { //線程池處於RUNNING狀態,將任務加入workQueue任務緩存隊列
            int recheck = ctl.get();    
            if (! isRunning(recheck) && remove(command))    //雙重檢查,若線程池狀態關閉了,移除任務
                reject(command);
            else if (workerCountOf(recheck) == 0)       //線程池狀態正常,但是沒有線程了,創建worker
                addWorker(null, false);
        }
        //第三步:創建臨時線程
        else if (!addWorker(command, false))
            reject(command);
    }

小結一下:execute()方法主要功能:

  1. 核心線程數量不足就創建核心線程;

  2. 核心線程滿了就加入緩存隊列;

  3. 緩存隊列滿了就增加非核心線程;

  4. 非核心線程也滿了就拒絕任務;

2.1.3.2、創建線程

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
​
            //等價於:rs>=SHUTDOWN && (rs != SHUTDOWN || firstTask != null || workQueue.isEmpty())
            //線程池已關閉,並且無需執行緩存隊列中的任務,則不創建
            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))  //CAS增加線程數
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (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 startable
                            throw 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;
    }

小結:addWorker()方法主要功能;

  1. 增加線程數;

  2. 創建線程Worker實例加入線程池;

  3. 加入完成開啟線程;

  4. 啟動失敗則回滾增加流程;

2.1.3.3、工作線程的實現

    private final class Worker  //Worker類是ThreadPoolExecutor的內部類
        extends AbstractQueuedSynchronizer  
        implements Runnable
    {
        
        final Thread thread;    //持有實際線程
        Runnable firstTask;     //worker所對應的第一個任務,可能為空
        volatile long completedTasks;   //記錄執行任務數
​
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }
        
        public void run() {
            runWorker(this);    //當前線程調用ThreadPoolExecutor中的runWorker方法,在這裡實現的線程復用
        }
​
        ...繼承AQS,實現了不可重入鎖...
    }

小結:工作線程Worker類主要功能;

  1. 此類持有一個工作線程,不斷處理拿到的新任務,持有的線程即為可復用的線程;

  2. 此類可看作一個適配類,在run()方法中真實調用runWorker()方法不斷獲取新任務,完成線程復用;

2.1.3.4、線程的復用

    final void runWorker(Worker w) {    //ThreadPoolExecutor中的runWorker方法,在這裡實現的線程復用
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;   //標識線程是否異常終止
        try {
            while (task != null || (task = getTask()) != null) {    //這裡會不斷從任務隊列獲取任務並執行
                w.lock();
                
                //線程是否需要中斷
                if ((runStateAtLeast(ctl.get(), STOP) ||    
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);    //執行任務前的Hook方法,可自定義
                    Throwable thrown = null;
                    try {
                        task.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); //執行任務後的Hook方法,可自定義
                    }
                } finally {
                    task = null;    //執行完成後,將當前線程中的任務制空,準備執行下一個任務
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);    //線程執行完成後的清理工作
        }
    }

小結:runWorker()方法主要功能;

  1. 迴圈從緩存隊列中獲取新的任務,直到沒有任務為止;

  2. 使用worker持有的線程真實執行任務;

  3. 任務都執行完成後的清理工作;

2.1.3.5、隊列中獲取待執行任務

    private Runnable getTask() {
        boolean timedOut = false;   //標識當前線程是否超時未能獲取到task對象
​
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
​
            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
​
            int wc = workerCountOf(c);
​
            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
​
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))      //若線程存活時間超時,則CAS減去線程數量
                    return null;
                continue;
            }
​
            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :   //允許超時回收則阻塞等待
                    workQueue.take();                                   //不允許則直接獲取,沒有就返回null
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

小結:getTask()方法主要功能;

  1. 實際在緩存隊列中獲取待執行的任務;

  2. 在這裡管理線程是否要阻塞等待,控制線程的數量;

2.1.3.6、清理工作

  private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();
​
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);          //移除執行完成的線程
        } finally {
            mainLock.unlock();
        }
​
        tryTerminate();     //每次回收完一個線程後都嘗試終止線程池
​
        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {    //到這裡說明線程池沒有終止
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);     //異常終止線程的話,需要在常見一個線程
        }
    }

小結:processWorkerExit()方法主要功能;

  1. 真實完成線程池線程的回收;

  2. 調用嘗試終止線程池;

  3. 保證線程池正常運行;

2.1.3.7、嘗試終止線程池

    final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            
            //若線程池正在執行、線程池已終止、線程池還需要執行緩存隊列中的任務時,返回
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
                
            //執行到這裡,線程池為SHUTDOWN且無待執行任務 或 STOP 狀態
            if (workerCountOf(c) != 0) {
                interruptIdleWorkers(ONLY_ONE);     //只中斷一個線程
                return;
            }
​
            //執行到這裡,線程池已經沒有可用線程了,可以終止了
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {  //CAS設置線程池終止
                    try {
                        terminated();   //執行鉤子方法
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));  //這裡將線程池設為終態
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

小結:tryTerminate()方法主要功能;

  1. 實際嘗試終止線程池;

  2. 終止成功則調用鉤子方法,並且將線程池置為終態。

2.2、JAVA線程池總結

以上通過對JAVA線程池的具體分析我們可以看出,雖然流程看似複雜,但其實有很多內容都是狀態重覆校驗、線程安全的保證等內容,其主要的功能與我們前面所提出的設計功能一致,只是額外增加了一些擴展,下麵我們簡單整理下線程池的功能;

2.2.1、主要功能

  1. 線程數量及存活時間的管理;

  2. 待處理任務的存儲功能;

  3. 線程復用機制功能;

  4. 任務超量的拒絕功能;

2.2.2、擴展功能

  1. 簡單的執行結果統計功能;

  2. 提供線程執行異常處理機制;

  3. 執行前後處理流程自定義;

  4. 提供線程創建方式的自定義;

2.2.3、流程總結

以上通過對JAVA線程池任務提交流程的分析我們可以看出,線程池執行的簡單流程如下圖所示;

2.3、JAVA線程池使用

線程池基本使用驗證上述流程:

   public static void main(String[] args) throws Exception {
        
        //創建線程池
       ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
               5, 10, 100, TimeUnit.SECONDS, new ArrayBlockingQueue(5));
        
        //加入4個任務,小於核心線程,應該只有4個核心線程,隊列為0
        for (int i = 0; i < 4; i++) {
            threadPoolExecutor.submit(new MyRunnable());
        }
        System.out.println("worker count = " + threadPoolExecutor.getPoolSize());   //worker count = 4
        System.out.println("queue size = " + threadPoolExecutor.getQueue().size()); //queue size = 0
        
        //再加4個任務,超過核心線程,但是沒有超過核心線程 + 緩存隊列容量,應該5個核心線程,隊列為3
        for (int i = 0; i < 4; i++) {
            threadPoolExecutor.submit(new MyRunnable());
        }
        System.out.println("worker count = " + threadPoolExecutor.getPoolSize());   //worker count = 5
        System.out.println("queue size = " + threadPoolExecutor.getQueue().size()); //queue size = 3
        
        //再加4個任務,隊列滿了,應該5個熱核心線程,隊列5個,非核心線程2個
        for (int i = 0; i < 4; i++) {
            threadPoolExecutor.submit(new MyRunnable());
        }
        System.out.println("worker count = " + threadPoolExecutor.getPoolSize());   //worker count = 7
        System.out.println("queue size = " + threadPoolExecutor.getQueue().size()); //queue size = 5
        
        //再加4個任務,核心線程滿了,應該5個熱核心線程,隊列5個,非核心線程5個,最後一個拒絕
        for (int i = 0; i < 4; i++) {
            try {
                threadPoolExecutor.submit(new MyRunnable());
            } catch (Exception e) {
                e.printStackTrace();    //java.util.concurrent.RejectedExecutionException
            }
        }
        System.out.println("worker count = " + threadPoolExecutor.getPoolSize());   //worker count = 10
        System.out.println("queue size = " + threadPoolExecutor.getQueue().size()); //queue size = 5
        System.out.println(threadPoolExecutor.getTaskCount());  //共執行15個任務
        
        //執行完成,休眠15秒,非核心線程釋放,應該5個核心線程,隊列為0
        Thread.sleep(1500);
        System.out.println("worker count = " + threadPoolExecutor.getPoolSize());   //worker count = 5
        System.out.println("queue size = " + threadPoolExecutor.getQueue().size()); //queue size = 0
        
        //關閉線程池
        threadPoolExecutor.shutdown();
    }

作者:京東零售 秦浩然

來源:京東雲開發者社區 轉載請註明來源


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

-Advertisement-
Play Games
更多相關文章
  • 1.內聯:寫在類內或外部聲明inline(編譯器判斷是否內聯,不是滿足上述條件就一定內聯),優點更快 2.protected:派生類可以直接調用基類的protected成員 3.class類內預設private,struct內預設public 4.構造函數最優寫法,用初始化(只有構造函數有)效率比在 ...
  • 1.Dos命令:dir:打出當前目錄結構;md:創建文件夾;cd+文件夾地址:跳轉到當前目錄下的對應文件夾;cd..:跳轉到上一目錄;rd+文件夾:刪除文件夾中東西;del+文件(或 “*.文件” 類型這樣的正則表達式):刪除文件或這類文件;cd/:跳轉到盤符;javac+文件名.java:編譯ja ...
  • 在使用 Selenium 操作 Chrome 瀏覽器時,如果 Chrome 瀏覽器閃退,則可能是以下幾個方面出現了問題: 1. Chromedriver 版本與 Chrome 瀏覽器版本不匹配 你需要確保你正在使用的 Chromedriver 版本與你的 Chrome 瀏覽器版本匹配。你可以在 Ch ...
  • 模擬滑鼠操作是模擬滑鼠點擊和鍵盤輸入的操作,UI自動化測試中非常實用。在Web UI、App UI、WinApp UI自動化測試講解中藉助Selenium和Appium框架下ActionChains、TouchAction、MouseButton等類已經介紹瞭如何模擬滑鼠和鍵盤操作。本文將為大家介紹 ...
  • alog是一個非常精簡的串口輸出日誌組件, 類似easyloger,但是比easyloger更簡單易用,只有2個實際不到百行的文件,實現了基本日誌所需的全部功能。 需移植配置的介面選項少,實現了串口輸出字元串就可以用了,沒有C庫以外的其他依賴。 沒有存儲日誌相關的擴展的API,適合新手使用理解和在資... ...
  • 通過將 Word 文檔轉換為 PDF,您可以確保文檔在不同設備上呈現一致,並防止其他人對文檔內容進行非授權修改。此外,在你需要列印文檔時,轉換為PDF還能確保列印輸出的準確性。本文將介紹如何使用Python 庫將Word文檔轉換為PDF格式。 Python 將 Word DOCX/DOC 轉換為 P ...
  • 一、BIO(Blocking I/O) BIO,同步阻塞IO模型,應用程式發起系統調用後會一直等待數據的請求,直至內核從磁碟獲取到數據並拷貝到用戶空間; 在一般的場景中,多線程模型下的BIO是成本較低、收益較高的方式。但是,如果在高併發的場景下,過多的創建線程,會嚴重占據系統資源,降低系統對外界響應 ...
  • 發現Java 21的StringBuilder和StringBuffer中多了repeat方法: /** * @throws IllegalArgumentException {@inheritDoc} * * @since 21 */ @Override public StringBuilder ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...