記一次ThreadPoolExecutor線程池的基本使用

最近再看多線程這方面的知識,所以對於線程池這個知識點是必須看的。所以用來做一個簡單的記錄。話不多說,先上代碼來。這裏的代碼都是簡單的實現,並沒有什麼特別的地方。當然,你也可以copy過去在自己的電腦上執行。


先寫一個實現 Runnable的類,用來執行

package com.yang.threadspringcase.config;

/*
 *@author:BaoYang
 *@Date: 2020/3/2
 *@description:
 */

public class GavinTask implements Runnable
{
    @Override
    public void run()
    {
        System.out.println("線程號 ---> " + Thread.currentThread().getName() + " GavinTask Running is OK.");
    }
}

 


CachedThreadPool 線程池
package com.yang.threadspringcase.config.threadpool;

/*
 *@author:BaoYang
 *@Date: 2020/3/2
 *@description:
 */

import com.yang.threadspringcase.config.GavinTask;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPoolExecutor
{

    public static void main(String[] args)
    {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for(int i=0;i<10;i++){
            executorService.execute(new GavinTask());
        }

        executorService.shutdown();
    }

}

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

這裏我們可以看到在 線程池 pool-1中用了十條線程去執行,這個就可以看出來,只要小於Integer.MAX_VALUE的任務,就會創建相應的線程數去執行。 這個你可以將十修改爲20,30看結果。


fixedThreadPool線程池

package com.yang.threadspringcase.config.threadpool;

/*
 *@author:BaoYang
 *@Date: 2020/3/2
 *@description:
 */

import com.yang.threadspringcase.config.GavinTask;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPoolExecutor
{
    public static void main(String[] args)
    {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        for(int i=0;i<100;i++) {
            fixedThreadPool.execute(new GavinTask());
        }

        fixedThreadPool.shutdown();
    }
}

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

可以看出來,線程池中的線程數編號是不會的大於五的,這是因爲在創建的時候,核心線程數 等於 最大線程線程數,然後使用一個LinkedBlockingQueue來進行存儲多餘的任務.


ScheduledThreadPool 線程池
package com.yang.threadspringcase.config.threadpool;

/*
 *@author:BaoYang
 *@Date: 2020/3/2
 *@description:
 */

import com.yang.threadspringcase.config.GavinTask;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolExecutor
{
    public static void main(String[] args)
    {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        // scheduledThreadPool.schedule(new GavinTask(),3, TimeUnit.SECONDS);
        for(int i=1;i<8;i++){
            scheduledThreadPool.schedule(new GavinTask(),i, TimeUnit.SECONDS);
        }
        scheduledThreadPool.shutdown();
    }
}

 

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

從執行的結果中可以看出來, 因爲沒有模擬耗時的業務來進行等待休眠等操作,所以可以看清晰的看出來,都是依次從核心線程數中的獲取執行的。這個主要是一個定時線程池。


SingleThreadPool 線程池
package com.yang.threadspringcase.config.threadpool;

/*
 *@author:BaoYang
 *@Date: 2020/3/2
 *@description:
 */

import com.yang.threadspringcase.config.GavinTask;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadExecutor
{

    public static void main(String[] args)
    {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for(int i=0;i<10;i++){
            singleThreadExecutor.execute(new GavinTask());
        }
        singleThreadExecutor.shutdown();
    }

}

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

從結果中就可以看出來,這個線程池裏面永遠就只有一個線程去執行。然後從其實現調用的方法就可以看出來,核心線程數和最大線程數都是1,多餘的任務使用LinkedBlockingQueue來進行存儲,然後後面一次從這個集合中獲取出任務進行執行.


自定義線程池

package com.yang.threadspringcase.config.threadpool;

/*
 *@author:BaoYang
 *@Date: 2020/3/2
 *@description:
 */

import com.yang.threadspringcase.config.GavinTask;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ExceptionThreadPool
{
    public static void main(String[] args)
    {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                1, 2, 0,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(1));
        for(int i=0;i<6;i++){
            threadPoolExecutor.execute(new GavinTask());
        }
    }
}

這是因爲需要執行的任務已經大於你自定的線程數導致的。

這個圖就可以很好的證明上面的代碼邏輯。然後你將6換成2,就不會有這個錯誤了。


然後這裏記錄一下這些參數的使用和基本的意義.

  1. corePoolSize : 核心線程數,當任務來了,會先看核心線程數是否還有,如果有的話,直接創建
  2. maximumPoolSize: 最大線程數, 當執行的任務比最大線程數還多的話,就會看到上面的錯誤。
  3. keepAliveTime: 保持活躍時間,線程在 keepAliveTime設置的時間內沒有執行任務,就會被線程池回收。
  4. unit : 對應3中的事件類行,是分鐘還是秒等
  5. workQueue: 隊列的情況(看你使用的那種隊列)

我們可以點擊進去看看源碼分析流程

ThreadPoolExecutor  --->  execute(Runnable command) 這個方法。可以使用這個翻譯來看下具體的信息

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }
  1. 先判斷傳入進來的 Runnable 是否爲 Null,如果是Null的話,就會拋出一個空指針異常
  2. 判斷corePoolSize是否還有,有的話 就會調用addWorker()方法,傳入 Runnable 和 true二個參數;如果成功就return,沒有成功就會走ctl.get() 賦值給c 然後程序繼續往下走

       2.1 : 繼續往下走的話;判斷是否Running 和 隊列中是否可以添加進去;如果可以的話,ctl.get() 獲取 recheck的值。

            2.1.1 判斷recheck的值是否running和並且remove掉runnable是成功的話,就會走 策略處理.  一般拋出異常。

            2.1.2 判斷工作個數是否等於 0 ,是的會走addWork(null,false) 傳入進入

      2.2  addWord(runnable,false) 來判斷是否啓動線程成功,如果不成功的話,就會走 策略處理。


這裏缺少核心的 addWord方法的說明,後續補上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章