十一、Java高級特性(阻塞隊列和線程池)

一、概念、生產者消費者模式

  • 隊列
    先進先出的一個數據結構
  • 阻塞隊列
    (1)當隊列爲空的時候,從裏面取數據的動作會被阻塞。
    (2)當隊列滿的時候,往裏面放元素的動作會被阻塞。
  • 生產者消費者模式
    在生產者和消費者模式之間插入一個阻塞隊列。生產者生產的東西放到隊列容器中,而消費者直接從隊列中取出東西進行消費。這樣可以使得生產者和消費者之間的解耦,也可以使得生產者和消費者的性能的均衡,避免生產者生產東西過快,一直等待消費者消費,或者消費者消費過快等待生產者生產東西。

二、JDK中實現的阻塞隊列

在JDK中BlockingQueue類封裝了阻塞隊列的接口
其中包含了幾個核心的方法:

  • add和remove方法,非阻塞方法
    當隊列滿的時候往裏面add數據,會拋出異常,當隊列爲空的時候從裏面remove數據,會拋出異常。
    boolean add(E e);
    boolean remove(Object o);
  • offer和poll方法,非阻塞方法
    offer:往隊列中裏插入一個元素,當隊列滿的時候,返回一個false,
    poll:從隊列取出一個元素,當隊列爲空的時候,返回一個null
    boolean offer(E e);
    E poll(long timeout, TimeUnit unit)
        throws InterruptedException;

  • take和put方法,真正意義上的阻塞方法
    take:當隊列爲空的時候,從隊列中取元素,會被阻塞
    put:當隊列滿的時候,往隊列中插入元素,會被阻塞
E take() throws InterruptedException;
void put(E e) throws InterruptedException;

三、JDK中常用的阻塞隊列

  • ArrayBlockingQueue
    是一個用數組實現的有界阻塞隊列。此隊列按照先進先出(FIFO)的原則對元素進行排序。默認情況下不保證線程公平的訪問隊列,所謂公平訪問隊列是指阻塞的線程,可以按照阻塞的先後順序訪問隊列,即先阻塞線程先訪問隊列。非公平性是對先等待的線程是非公平的,當隊列可用時,阻塞的線程都可以爭奪訪問隊列的資格,有可能先阻塞的線程最後才訪問隊列。初始化時有參數可以設置

  • LinkedBlockingQueue
    是一個用鏈表實現的有界阻塞隊列。此隊列的默認和最大長度爲Integer.MAX_VALUE。此隊列按照先進先出的原則對元素進行排序。

  • PriorityBlockingQueue
    PriorityBlockingQueue是一個支持優先級的無界阻塞隊列。默認情況下元素採取自然順序升序排列。也可以自定義類實現compareTo()方法來指定元素排序規則,或者初始化PriorityBlockingQueue時,指定構造參數Comparator來對元素進行排序。需要注意的是不能保證同優先級元素的順序。

  • DelayQueue
    是一個支持延時獲取元素的無界阻塞隊列。隊列使用PriorityQueue來實現。隊列中的元素必須實現Delayed接口,在創建元素時可以指定多久才能從隊列中獲取當前元素。只有在延遲期滿時才能從隊列中提取元素。

  • SynchronousQueue:一個不存儲元素的阻塞隊列。

  • LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。

  • LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。

以上的阻塞隊列都實現了BlockingQueue接口,也都是線程安全的。

在使用阻塞隊列的時候,儘量使用有界的阻塞隊列,有界的阻塞隊列,規定了最大容量,當隊列滿的時候,往裏面插入元素,會被阻塞。而無界的阻塞隊列,可以一直往裏插入元素,會佔用內存,最終總會使得內存溢出。

緩存系統的設計

可以用DelayQueue保存緩存元素的有效期,使用一個線程循環查詢DelayQueue。一旦可以從隊列中查到元素,表示緩存有效期到了。

四、線程池

Java中的線程池是運用場景最多的併發框架。合理的使用線程池能夠帶來以下3個好處:
(1)降低資源的消耗:通過重複利用線程池中的線程,降低線程創建和銷燬帶來的開銷。
(2)提高響應速度:當提交一個任務的時候,不需要等待線程的創建就能立即執行。
(3)提高線程的可管理性:在操作系統中,線程是非常稀缺的資源,如果無限制的創建線程,不僅會消耗系統資源,還會降低系統的穩定性。

1、JDK中線程池的實現

在JDK中使用ThreadPoolExecutor 作爲線程池的核心類,用來執行被提交的任務。

ThreadPoolExecutor 的使用舉例
package com.it.test.thread.consumer_product.retranlock;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class LockTest {
    public static void main(String[] args) {
        LinkedBlockingQueue queue = new LinkedBlockingQueue();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 30, TimeUnit.MINUTES, queue, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r);
            }
        }, new ThreadPoolExecutor.DiscardOldestPolicy());
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+":你好");
            }
        });
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+":世界");
            }
        });
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+":hello");

            }
        });
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+":world");
            }
        });
        System.out.println("阻塞隊列的長度:"+queue.size());
    }
}

pool-1-thread-1:你好
pool-1-thread-2:世界
pool-1-thread-2:world
pool-1-thread-1:hello
阻塞隊列的長度:2
ThreadPoolExecutor 類的解析

(1)構造方法
構造方法中包含了7個核心參數

  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 
  • corePoolSize
    核心線程數,當提交一個任務的時候,默認情況下線程池會新建一個線程執行任務,直到線程數等於核心線程數。
    如果當前線程數爲corePoolSize(核心線程數),繼續提交任務的時候,會被保存到阻塞隊列中等待被執行。
    如果執行了線程池的prestartAllCoreThreads方法,線程池會提前創建並啓動所有的核心線程。

  • maximumPoolSize
    線程池允許的最大線程數,如果當阻塞隊列中的任務滿的時候,繼續提交任務,線程池會繼續創建新的線程執行任務。前提是當前線程數要小於等於最大線程數。

  • keepAliveTime
    線程空閒的時候,存活的時間。即當沒有任務執行的時候,線程繼續存活的時間。默認情況下該參數只有在線程數大於核心線程數纔有用

  • TimeUnit
    時間單位

  • workQueue
    存放任務的BlockingQueue阻塞隊列,如果當前線程數量等於核心線程數,繼續提交任務的時候,會將任務存放到阻塞隊列,等待被執行。

  • threadFactory
    線程池中創建線程的工廠,一般只是對線程做一些策略,例如自定義線程名字等等

  • handler
    拒絕策略,當線程池中的線程超出最大線程數maximumPoolSize的時候,繼續提交任務,會觸發拒絕策略。在JDK中有四種拒絕策略的核心類:

(1)AbortPolicy:直接拋出異常,默認策略;
(2)CallerRunsPolicy:用調用者所在的線程來執行任務;
(3)DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,並執行當前任務;
(4)DiscardPolicy:直接丟棄任務;

線程池的工作機制

(1)如果運行的線程池少於核心線程數,提交任務的時候將創建新的線程來執行任務。
(2)如果運行的線程數等於核心線程數,提交任務的時候將會把任務存放到阻塞隊列中(BlockingQueue)。
(3)如果阻塞隊列滿的時候,無法再添加,則創建新的線程來執行任務。
(4)如果創建的線程超過最大線程數,將會被拒絕。調用RejectedExecutionHandler.rejectedExecution()方法

提交任務

execute提交任務,不需要任務返回值。所以無法判斷任務是否被線程池執行成功
submit提交任務,有返回值,返回一個future類型的對象。可以通過future對象判斷任務是否執行成功,通過future的get方法,獲取任務的返回值。在調用get方法的時候,會阻塞當前線程,直到任務完成爲止。也可以在get方法設置返回時間。

關閉線程池

可以通過shutdown和shutdownNow來關閉線程池。

合理的配置線程池

合理配置線程池之前首先要分析任務的特性:
(1)CPU密集型
核心線程數 = Ncpu+1,
(2)IO密集型
核心線程數 = 2*Ncpu
(3)混合型
可以根據不同的任務類型對線程池進行拆分多個。

ThreadPoolExecutor源碼分析
  • 構造方法
    傳入核心線程數,阻塞隊列等參數,設置到當前成員
    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;
    }
  • 提交任務
    根據設置的核心線程數和阻塞隊列判斷是否要調用addWorker添加任務還是reject拒絕任務
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();

        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);
    }
  • 添加任務
private boolean addWorker(Runnable firstTask, boolean core) {
      .......
        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 c = ctl.get();

                    if (isRunning(c) ||
                        (runStateLessThan(c, STOP) && firstTask == null)) {
                        if (t.getState() != Thread.State.NEW)
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        workerAdded = true;
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

(1)w = new Worker(firstTask);
創建一個Worker對象,在Worker構造方法中創建一個線程,傳入Worker當前對象,Worker實現了Runnable接口

   Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

(2)添加Worker到HashSet中(HashSet<Worker> workers),添加成功則啓動woker中的線程

 workers.add(w);
....
  if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }

因爲Worker實現了Runnable接口,並且在創建線程的時候傳入了當前Worker對象,所以啓動線程的時候調用了Worker中的run方法

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

接着調用runWorker方法

   final void runWorker(Worker w) {
        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);
                    try {
                        task.run();
                        afterExecute(task, null);
                    } catch (Throwable ex) {
                        afterExecute(task, ex);
                        throw ex;
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

runWorker方法中,開啓了一個while 循環,獲取第一次傳進的任務或者是
getTask(),阻塞隊列中的任務。然後調用 task.run();這樣就回調到了我們提交任務的時候實現的run方法。

源碼小結

在ThreadPoolExecutor中用一個HashSet用來存放woker,而woker的構造方法中創建了線程,因此可以理解爲woker存放了核心線程。當HashSet的長度等於核心線程數的時候,則將任務提交到阻塞隊列。
在執行任務的時候獲取當前提交的task或者從阻塞隊列中取出task執行。

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