Java多線程編程-9 J.U.C之線程池

1、什麼是線程池

程序的世界裏有各種池如:數據庫連接池、HTTP連接池以及我們現在要說的這個線程池。池的作用很明顯,就是用來存放一些比較重的資源,像獲取數據庫連接、http連接、開啓新的線程,這些資源的獲取及關閉都會耗費大量的內存及時間,對系統的性能會產生比較大的影響。

池化的作用便是對這些資源進行統一的管理,如線程池的管理:當有多個併發請求時,開啓多個線程同時進行處理,當線程中的任務處理結束時,不是馬上銷燬線程,而是讓其繼續進行調度,當滿足一定條件時才銷燬。這樣,當有新的任務進入,不必重新開啓新的線程,直接從池子裏取出之前的線程用於本次任務處理,提高了任務的處理速度並減少系統因頻繁開啓和關閉線程浪費系統資源。

2、J.U.C中的線程池

2.1 ThreadPoolExecutor

先來看構造函數:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
屬性 說明
corePoolSize 核心線程大小,最小工作線程數
maximumPoolSize 最大開啓線程大小,當達到最大線程大小,線程池不會再開啓新的線程,新的任務將會加入到任務隊列等待處理
keepAliveTime 當開啓的線程數大於corePoolSize時,空閒的線程最大的存活時間
unit 存活時間的單位,可以是秒、分、時、天等
workQueue 工作隊列,線程來不及處理的任務就是先放在這裏排隊

上面的表格對該線程池的重要參數做了簡要說明,Executors提供的靜態方法其實就是一個對常用的線程池參數進行簡單的封裝:

方法 說明
newFixedThreadPool(int nThreads) new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue()),固定大小的線程池
newSingleThreadExecutor() new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue())),只有一個線程的線程池
newCachedThreadPool() new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()),緩存的線程池

newFixedThreadPool(10):固定大小爲10的線程池,可以看到核心線程數=最大線程數,意味着線程池中的線程不會被回收(詳見keepAliveTime字段說明),每次有新任務提交到線程池,都會開啓一個新的線程,知道線程數已經達到最大的10個線程。然後該線程池會一直存在10個線程,不管有沒有處理任務。

newSingleThreadExecutor:只有一個線程的線程池,和newFixedThreadPool方法類似。可以看到這個線程池還用FinalizableDelegatedExecutorService這個類封裝後再返回,原因是因爲只有一個線程,不需要線程監控的功能。

newFixedThreadPool和newSingleThreadExecutor兩種線程池都是用LinkedBlockingQueue阻塞隊列,這個隊列是一個鏈表類型的隊列,當任務處理不及時時會一直插入隊列的尾部,可能會造成任務的積壓

newCachedThreadPool:緩存的線程池能夠將空閒的線程緩存60s(見構造函數),開啓的最大的線程數爲Integer.MAX_VALUE,再看其隊列的類型爲SynchronousQueue,這個隊列是同步隊列,裏面只能存放一個任務,當任務處理很慢且存在很多任務時,該線程池可能來不及處理,導致一直開啓新的線程,直到達到最大值,發生這種情況對系統是一種災難!

綜上,我們在使用ThreadPoolExecutor這個線程池的時候必須根據業務的特點:
1、使用其構造函數構造自己需要的線程池,設置合理的核心線程和最大線程,以及keepAliveTime和隊列類型。
2、將業務分爲核心業務和普通業務2個或多個線程池,避免非核心業務對核心業務的處理造成影響。

2.2 ForkJoinPool

【小家java】Java線程池之—ForkJoinPool線程池的使用以及原理

3、自己動手寫一個支持動態拓展的線程池

參考ThreadPoolExecutor(java.util.concurrent.ThreadPoolExecutor#addWorker、java.util.concurrent.ThreadPoolExecutor#runWorker、java.util.concurrent.ThreadPoolExecutor#getTask、java.util.concurrent.ThreadPoolExecutor#tryTerminate),實現一個簡單版的可拓展線程池。原理:
1、構造函數參數支持核心線程數、最大線程數、空閒線程存活時間,隊列使用LinkedList鏈表(爲了模擬線程池動態增減,設置了最大排隊10個任務)
2、每次提交任務到線程池,根據核心和最大線程選擇開啓新的線程或加入到任務隊列
3、每次任務處理結束後,輪詢從隊列中取任務,當輪詢時間超過keepAliveTime,嘗試關閉線程池中空閒線程。(與ThreadPoolExecutor不同,ThreadPoolExecutor採用的是workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS),詳見java.util.concurrent.ThreadPoolExecutor#getTask方法)

完整代碼如下:

public class SimpleThreadPoolExecutor implements Executor {

    private volatile int corePoolSize;

    private volatile int maximumPoolSize;

    private volatile long keepAliveTime;

    private final ReentrantLock takeLock = new ReentrantLock();
    private final LinkedList<Runnable> workQueue;
    private final int maxQueueSize = 10; // 隊列最大排隊任務數

    private final HashSet<Worker> workers = new HashSet<Worker>();

    private ReentrantLock reentrantLock = new ReentrantLock();

    private ThreadFactory threadFactory;
    /**
     * 當前線程池大小
     */
    private volatile int currentPoolSize = 0;

    public SimpleThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime) {
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.keepAliveTime = keepAliveTime;
        this.workQueue = new LinkedList<>();
        this.threadFactory = new DefaultThreadFactory();
    }

    public int getCurrentPoolSize() {
        return currentPoolSize;
    }

    public HashSet<Worker> getWorkers() {
        return workers;
    }

    public int getQueueSize() {
        return workQueue.size();
    }

    public class Worker implements Runnable {

        Runnable firstTask;

        private final Thread thread;

        private final ReentrantLock lock = new ReentrantLock();
        /**
         * 線程是否空閒
         */
        private volatile boolean isIdle = false;

        public Worker(Runnable firstTask) {
            this.firstTask = firstTask;
            this.thread = threadFactory.newThread(this);
        }

        public boolean isIdle() {
            return isIdle;
        }

        @Override
        public void run() {
            runWorker(this);
        }

        private void lock() {
            lock.lock();
        }

        private void unlock() {
            lock.unlock();
        }
    }

    @Override
    public void execute(Runnable command) {
        if (currentPoolSize < corePoolSize) {
            addWorker(command);
        }
        final ReentrantLock lock = this.reentrantLock;
        lock.lock();
        try {
            if (workers.size() > 0 && workQueue.size() <= maxQueueSize) {
                workQueue.addLast(command);
                return;
            }
        } finally {
            lock.unlock();
        }
        if (currentPoolSize < maximumPoolSize) {
            addWorker(command);
        } else if (!addWorker(command)) {
            lock.lock();
            try {
                workQueue.addLast(command);
            } finally {
                lock.unlock();
            }
        }
    }

    private boolean addWorker(Runnable task) {
        final ReentrantLock lock = this.reentrantLock;
        lock.lock();
        Worker worker = new Worker(task);
        try {
            if (currentPoolSize + 1 > maximumPoolSize) {
                return false;
            }
            this.workers.add(worker);
            currentPoolSize++;
        } finally {
            lock.unlock();
        }
        Thread t = worker.thread;
        if (t != null) {
            t.start();
        }
        return true;
    }

    private void runWorker(Worker worker) {
        Runnable task = worker.firstTask;
        long elapsedTime = 0;
        long eachSleepTime = 10;
        try {
            for (; ; ) {
                while (task != null || (task = getTask()) != null) {
                    worker.lock();
                    try {
                        task.run();
                        worker.isIdle = false;
                        task = null;
                    } finally {
                        worker.unlock();
                    }
                }
                // 休眠一會
                sleep(eachSleepTime);
                if ((task = getTask()) != null) {
                    // 重新開始計算空閒時間
                    elapsedTime = 0;
                    continue;
                }
                elapsedTime += eachSleepTime;
                if (elapsedTime >= keepAliveTime * 1000) {
                    worker.lock();
                    worker.isIdle = true;
                    worker.unlock();
                    break;
                }
            }
        } finally {
            closeIdleThread();
        }
    }

    /**
     * 關閉空閒線程
     */
    private void closeIdleThread() {
        reentrantLock.lock();
        try {
            if (currentPoolSize > corePoolSize) {
                Iterator<Worker> $it = workers.iterator();
                while ($it.hasNext()) {
                    Worker worker = $it.next();
                    worker.lock();
                    try {
                        if (worker.isIdle) {
                            worker.thread.interrupt();
                            $it.remove();
                            currentPoolSize--;
                        }
                    } finally {
                        worker.unlock();
                    }
                }
            }
        } finally {
            reentrantLock.unlock();
        }
    }

    /**
     * 從隊列中取出任務
     *
     * @return
     */
    private Runnable getTask() {
        takeLock.lock();
        try {
            Runnable task = workQueue.removeFirst();
            return task;
        } catch (Exception e) {
            //
            return null;
        } finally {
            takeLock.unlock();
        }
    }

    /**
     * 線程工廠:默認創建守護線程
     */
    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                    Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                    poolNumber.getAndIncrement() +
                    "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                    namePrefix + threadNumber.getAndIncrement(),
                    0);
            t.setDaemon(true);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

    private void sleep(long milliSeconds) {
        try {
            TimeUnit.MILLISECONDS.sleep(milliSeconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

測試類:

public class ThreadPoolClient {

    public static void main(String[] args) throws IOException {
        SimpleThreadPoolExecutor executor = new SimpleThreadPoolExecutor(1, 6, 5L);
        AtomicInteger seq = new AtomicInteger(0);
        for(int i = 0; i < 20; i++) {
            executor.execute(() -> {
                sleep(1000);
                System.out.println("hello" + seq.getAndIncrement());
            });
        }
        new Thread(() -> {
            while (true) {
                sleep(1000);
                System.out.println(String.format("CurrentPoolSize->%s, QueueSize->%s", executor.getCurrentPoolSize(), executor.getQueueSize()));
            }
        }).start();
        sleep(10000);
        System.out.println("繼續提交任務");
        for(int i = 0; i < 20; i++) {
            executor.execute(() -> {
                sleep(1000);
                System.out.println("hello" + seq.getAndIncrement());
            });
        }

        System.in.read();
    }

    private static void sleep(long milliSeconds) {
        try {
            TimeUnit.MILLISECONDS.sleep(milliSeconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章