Java多線程與高併發(三)

爲什麼要使用線程池?

原因:線程不斷創建和銷燬過程很佔用系統資源,如果管理不善很容易導致系統一系列問題發生,因此大多併發框架中都會使用線程池。

好處:

  1. 使用線程池可以重複使用已有得線程繼續執行任務,避免線程創建和銷燬時造成的消耗。
  2. 由於沒有線程創建和銷燬的消耗,可以極大提高系統響應速度。
  3. 通過線程池可以很好的對線程合理的管理,根據系統的承受能力調整可運行線程數量大小等,因爲線程若是無限制的創建,可能會導致內存佔用過多而產生OOM,並且會造成cpu過度切換(cpu切換線程是有時間成本的(需要保持當前執行線程的現場,並恢復要執行線程的現場))
  4. 線程池提供更強大的功能,延時定時線程池。 

線程和隊列的關係

線程池執行所提交的任務過程

先判斷線程池中的核心線程池所有的線程是否都在執行任務。如果不是,則新建一個線程執行剛提交的任務,否則,在判斷當前阻塞隊列是否已滿,如果未滿,則將提交的任務放置在阻塞隊列中,否則,在判斷線程池所有的線程是否都在執行任務如果沒有,則創建一個新的線程來執行任務,否則,則交由飽和策略進行處理

 

線程池的分類

1.ThreadPoolExecutor

  • newCachedThreadPool(無限線程池,具有自動線程回收)

創建一個可根據需要創建的線程池。但是在以前構造的線程可用時將重用它,需要喜歡創建新的線程ThreadFactory創建。

特點:

  1. 線程池中的數量沒有固定,可達最大Integer.Max_VALUE.
  2. 線程池中的線程可重複利用和回收【回收默認時間爲一分鐘】
  3. 當線程池沒有線程可用時,會創建一個新的線程來執行提交任務。

代碼示例:

線程類.Java

package com.juc.pool;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/30 18:57
 * @Description 線程
 */
public class Task implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "----running");
    }
}
package com.juc.pool;
import java.util.concurrent.ExecutorService;
import static java.util.concurrent.Executors.*;
/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/30 17:04
 * @Description 無限線程池,具有自動線程回收
 * newCachedThreadPool()
 * 創建一個根據需要創建新線程的線程池,但在可用時將重新使用以前構造的線程。
 */
public class CacheThreadPoolDemo {
    public static void main(String[] args) {
        //
        ExecutorService executorService = newCachedThreadPool();
        for (int i = 1; i <=20; i++) {
            executorService.execute(new Task());
        }

        //關閉線程池
        executorService.shutdown();
    }
}
  • newFixedThreadPool(固定大小的線程池
package com.juc.pool;

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

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/30 19:16
 * @Description創建一個線程池,該線程池重用固定數量的從共享無界隊列中運行的線程。 在任何時候,最多nThreads線程將處於主動處理任務。
 * 如果所有線程處於活動狀態時都會提交其他任務,則它們將等待隊列中直到線程可用。
 * 如果任何線程由於在關閉之前的執行期間發生故障而終止,則如果需要執行後續任務,則新線程將佔用它。
 * 池中的線程將存在,直到它明確地爲shutdown 。
 */
public class FixedThreadPoolDemo {
    public static void main(String[] args) {
        //nThreads 指定線程大小
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
            //執行任務
            executorService.execute(new Task());
        }
        //關閉線程池
        executorService.shutdown();
    }
}

創建一個可重複固定的線程池,以共享的無界隊列來運用這些線程,在任意點,在大多數。nThreads 線程會處於處理任務的活動狀態,如果在線程處於活動狀態提交附加任務,則在可用線程之前,附加任務在隊列中等待,如果在關閉執行任務期間由於失敗而導致任何線程終止,那麼新的一個線程將代替它執行後續任務,在某個線程被顯式地關閉之前,線程池的的線程一直存在。

特點:

      1.線程池中的線程處於一定的量,可以很好的控制線程的併發量。

      2.線程可以重複被使用,在顯式關閉之前,都將一直存在。

      3.超出一定量的線程被提交的時候需要隊列中等待。

  • newSingleThreadPool(單個後臺線程)

package com.juc.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*@Author zcm
*@Email [email protected]
*@date 2020/5/30 19:24
*@Description創建一個使用從無界隊列運行的單個工作線程的執行程序。
 *  (請注意,如果這個單個線程由於在關閉之前的執行過程中發生故障而終止,
 *  則如果需要執行後續任務,則新的線程將佔用它。)
 * 任務保證順序執行,並且不超過一個任務將被激活在任何給定的時間。
 * 與其他等效的newFixedThreadPool(1) newFixedThreadPool(1) ,
 * 返回的執行器保證不被重新配置以使用額外的線程。
*/
public class SingleThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
    for (int i = 0; i <20 ; i++) {
            //執行任務
        executorService.execute(new Task());
        }
        //關閉線程池
        executorService.shutdown();
    }
}

創建一個使用單個的Worker線程的Executor,以無界隊列方式來運行該線程。(注意:如果因爲再關閉之前的執行期間出現失敗而終止此單個線程,那麼如果需要,一個新線程將代替它執行後續任務),可保證順序低執行各個任務,並且在任意給定的時間不會有多個線程是活動的,與其他等效的newFixedThreadPool(1)不同,可保證無需要重新配置此方所返回的執行線程程序可使用其他的線程。’

特點:

        1.線程池最多執行一個線程,之後所提交的線程活動將會排在隊列中此執行。

2.ScheduledThreadPoolExecutor

  • newSingleThreadScheduledExecutor
package com.juc.pool;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
*@Author zcm
*@Email [email protected]
*@date 2020/5/30 19:36
*@Description創建一個單線程執行器,可以調度命令在給定的延遲之後運行,或定期執行。
 * (請注意,如果這個單個線程由於在關閉之前的執行過程中發生故障而終止,則如果需要執行後續任務,則新的線程將佔用它。)
 * 任務保證順序執行,並且不超過一個任務將被激活在任何給定的時間。
 * 與其他等效的newScheduledThreadPool(1) newScheduledThreadPool(1) ,
 * 返回的執行器保證不被重新配置以使用額外的線程。
 *
 */
public class SingleThreadScheduledExecutorDemo {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        System.out.println("執行前當前時間:" + System.currentTimeMillis());
        //延遲三秒執行
        scheduledExecutorService.schedule(()->  System.out.println("延遲三秒執行"), 3, TimeUnit.SECONDS);
        scheduledExecutorService.shutdown();
        System.out.println("執行後當前時間:" + System.currentTimeMillis());
    }
}

創建一個線程池,它可安排在給定延遲運行命令或者定期地執行。

特點:

         1.線程池中具有指定數量的線程,即使時空線程也將保留。

          2.可定時或者延遲執行線程活動。

  • newScheduledThreadPool
package com.juc.pool;

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

import static java.util.concurrent.Executors.*;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/30 19:36
 * @Description創建一個線程池,可以調度命令在給定的延遲之後運行,或定期執行。 
 * scheduleAtFixedRate  延時和定時使用這個方法
 * schedule 延遲使用這個方法
 * 注意scheduleAtFixedRate使用不要關掉shutdown線程池,不然就不會延時和定時執行
 * 多任務執行的時候,隨機搶佔資源
 */
public class ScheduledThreadPoolDemo {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = newScheduledThreadPool(3);
        System.out.println("執行前當前時間:" + System.currentTimeMillis());
        //延遲三秒執行
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("延遲一秒執行,每三秒執行");
            System.out.println("執行後當前時間:" + System.currentTimeMillis());
        }, 1, 3, TimeUnit.SECONDS);
//        scheduledExecutorService.shutdown();

    }
}

創建一個單線程執行程序,它可安排在給定延遲後運行命令或者定期地執行。

特點:

         1.線程池中最多執行一個線程,之後提交的線程活動將會排在隊列中執行。

          2.可定時或者延遲線程活動。

3.ForkJoinPool

  • newWorkStealingPool

任務代碼

package com.juc.pool.fork;

import java.util.concurrent.RecursiveAction;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/30 20:57
 * @Description 拆分任務
 */
public class PrintTask extends RecursiveAction {
    /**
     *最多打印50次
     */
    private static final int THRESHOLD = 50;
    private int start;
    private int end;

    public PrintTask(int start, int end) {
        super();
        this.start = start;
        this.end = end;
    }

    @Override
    protected void compute() {
        if (end - start < THRESHOLD) {
            for (int i = start; i < end; i++) {
                System.out.println(Thread.currentThread().getName() + "的i值:" + i);
            }
        } else {
            int middle = (start + end) / 2;
            PrintTask left = new PrintTask(start, middle);
            PrintTask right = new PrintTask(middle, end);
            //並行執行兩個小任務
            left.fork();
            right.fork();
        }
    }
}

調用Task

 

package com.juc.pool.fork;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/30 20:50
 * @Description創建一個維護足夠的線程以支持給定的並行級別的線程池,並且可以使用多個隊列來減少爭用。 並行級別對應於主動參與或可以從事任務處理的最大線程數。 線程的實際數量可以動態增長和收縮。
 * 工作竊取池不保證執行提交的任務的順序。
 * 打印0~300 的數 切分50次
 */
public class WorkStealingPoolDemo {
    public static void main(String[] args) {
        PrintTask task = new PrintTask(0, 300);
        //創建示例,並執行分割任務
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        forkJoinPool.submit(task);
        //線程阻塞,等待任務執行完成
        forkJoinPool.awaitQuiescence(2, TimeUnit.SECONDS);
        //關閉線程
        forkJoinPool.shutdown();
    }
}

核心線程池和最大線程池大小

ThreadPoolExecutor將自動調整池大小 getCorePoolSize()maximumPoolSize。 當方法execute(Runnable)中提交了新任務,並且運行的corePoolSize線程少於一個,即使其他工作線程處於空閒狀態,也會創建一個新的線程來處理該請求。 如果超過corePoolSize但小於maximumPoolSize線程運行,則僅當隊列已滿時纔會創建一個新線程。 通過將corePoolSizemaximumPoolSize設置爲相同,您將創建一個固定大小的線程池。 通過將maximumPoolSize設置爲本質上無限制的值(如Integer.MAX_VALUE ,您可以允許池容納任意數量的併發任務。 最典型的是,核心線程池和最大線程池大小隻能在構建時進行設置,但也可以使用setCorePoolSize(int)setMaximumPoolSize(int)進行動態更改 。 

默認情況下,即使核心線程最初創建並且只有在新任務到達時才啓動,但是可以使用方法prestartCoreThread()或prestartAllCoreThreads()動態地覆蓋 。 如果您使用非空隊列構建池,則可能需要預先提供線程。

創建新線程

新線程使用ThreadFactory創建。 如果沒有另外指定,則使用Executors.defaultThreadFactory() ,它創建所有線程與所有相同的ThreadGroup並且具有相同的優先級和非守護進程狀態NORM_PRIORITY 。 通過提供不同的ThreadFactory,您可以更改線程的名稱,線程組,優先級,守護進程狀態等。如果ThreadFactory在從newThread返回null請求時無法創建線程,則執行程序將繼續,但可能無法執行任務 線程應該擁有“modifyThreadRuntimePermission 。 如果使用池的工作線程或其他線程不具有此權限,則服務可能會降級:配置更改可能不會及時生效,並且關閉池可能仍處於可能終止但未完成的狀態。

如果池當前具有多於corePoolSize線程,則如果空閒超過keepAliveTime,則多餘的線程將被終止。 這提供了當線程池未被主動使用時減少資源消耗的方法。 如果稍後線程池變得更加活躍,將構建新的線程。 此參數也可以使用方法setKeepAliveTime(long, TimeUnit)動態更改 。 使用值Long.MAX_VALUE TimeUnit.NANOSECONDS有效地禁用空閒線程在關閉之前終止。 默認情況下,僅當存在多corePoolSize線程時,保持活動策略才適用。 但是方法allowCoreThreadTimeOut(boolean)也可以用於將這個超時策略應用於核心線程,只要值不爲零。keepAliveTime

排隊

任何BlockingQueue可用於傳送和保留提交的任務。 這個隊列的使用與池大小相互作用:

  • 如果少於corePoolSize線程正在運行,Executor總是喜歡添加一個新線程,而不是排隊。
  • 如果corePoolSize或更多的線程正在運行,Executor總是喜歡排隊請求而不是添加一個新的線程。
  • 如果請求無法排隊,則會創建一個新線程,除非這將超出maximumPoolSize,否則任務將被拒絕。

排隊的三種策略:
        1.直接切換 一個工作隊列的一個很好的默認選擇是一個SynchronousQueue ,將任務交給線程,無需另外控制。 在這裏,如果沒有線程可以立即運行,那麼嘗試排隊任務會失敗,因此將構建一個新的線程。 處理可能具有內部依賴關係的請求集時,此策略可避免鎖定。 直接切換通常需要無限制的maximumPoolSizes,以避免拒絕新提交的任務。 這反過來允許無限線程增長的可能性,當命令繼續以平均速度比他們可以處理的速度更快地到達時。
        2.無界隊列 使用無界隊列(LinkedBlockingQueue沒有預定容量)會導致新的任務,在隊列中等待,當所有corePoolSize線程都很忙。 因此,不會再創建corePoolSize線程。 (因此,最大值大小的值沒有任何影響。)每個任務完全獨立於其他任務時,這可能是適當的,因此任務不會影響其他執行; 例如,在網頁服務器中。 雖然這種排隊風格可以有助於平滑瞬態突發的請求,但是當命令繼續達到的平均速度比可以處理的速度更快時,它承認無界工作隊列增長的可能性。
       3.有邊界的隊列。 有限隊列 ArrayBlockingQueue )有助於在使用有限maxPoolSizes時防止資源耗盡,但可能更難調整和控制。 隊列大小和最大池大小可能彼此交易:使用大隊列和小型池可以最大限度地減少CPU使用率,OS資源和上下文切換開銷,但可能導致人爲的低吞吐量。 如果任務頻繁阻塞(如果它們是I / O綁定),則系統可能能夠安排比您允許的更多線程的時間。 使用小型隊列通常需要較大的線程池大小,這樣可以使CPU繁忙,但可能會遇到不可接受的調度開銷,這也降低了吞吐量。

拒絕策略

方法execute(Runnable)中提交的新任務將在執行程序關閉時被拒絕 ,並且當執行程序對最大線程和工作隊列容量使用有限邊界並且飽和時。 在任何一種情況下, execute方法調用RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)其的方法RejectedExecutionHandler 。 提供了四個預定義的處理程序策略: 
    1、在默認ThreadPoolExecutor.AbortPolicy ,處理程序會引發運行RejectedExecutionException後排斥反應。 
    2、ThreadPoolExecutor.CallerRunsPolicy中,調用execute本身的線程運行任務。 這提供了一個簡單的反饋控制機制,將降低新務   提交的速度。 
    3、ThreadPoolExecutor.DiscardPolicy中 ,簡單地刪除無法執行的任務。 
    4、ThreadPoolExecutor.DiscardOldestPolicy中 ,如果執行程序沒有關閉,則工作隊列頭部的任務被刪除,然後重試執行(可能會再次失敗,導致重複)。 
   5、可以定義和使用其他類型的RejectedExecutionHandler類。 這樣做需要特別注意,特別是當策略被設計爲僅在特定容量或排隊策略下工作時。 

鉤子方法 
該類提供了在每個任務執行之前和之後調用的protected覆蓋的(TbeforeExecutehread, Runnable)afterExecute(Runnable, Throwable)方法。 這些可以用來操縱執行環境; 例如,重新初始化ThreadLocals,收集統計信息或添加日誌條目。 另外,方法terminated()可以被覆蓋,以執行執行程序完全終止後需要執行的任何特殊處理。 
如果鉤子或回調方法拋出異常,內部工作線程可能會失敗並突然終止。 

隊列維護 
方法getQueue()允許訪問工作隊列以進行監視和調試。 強烈不鼓勵將此方法用於任何其他目的。 當提供大量排隊任務被取消時,兩種提供的方法remove(Runnable)purge()可用於協助進行存儲回收。 
即不再在程序中引用, 並沒有剩餘的線程將成爲線程池shutdown自動。 如果希望確保未引用的線程池被回收,即使忘記調用shutdown() 則必須安排未使用的線程最終死機,通過設置適當的保持活動時間,使用零個核心線程的下限和/或設置allowCoreThreadTimeOut(boolean) 。 

線程池的生命週期

RUNNING :能接受新提交的任務,並且也能處理阻塞隊列中的任務;
SHUTDOWN:關閉狀態,不再接受新提交的任務,但卻可以繼續處理阻塞隊列中已保存的任務。
STOP:不能接受新任務,也不處理隊列中的任務,會中斷正在處理任務的線程。
TIDYING:如果所有的任務都已終止了,workerCount (有效線程數) 爲0,線程池進入該狀態後會調用 terminated() 方法進入TERMINATED 狀態。
TERMINATED:在terminated() 方法執行完後進入該狀態,默認terminated()方法中什麼也沒有做。

線程池的創建

  •  corePoolSize:核心線程池的大小
  •  maximumPoolSize:線程池能創建線程的最大個數
  •  keepAliveTime:空閒線程存活時間
  •  unit:時間單位,爲keepAliveTime指定時間單位
  •  workQueue:阻塞隊列,用於保存任務的阻塞隊列
  •  threadFactory:創建線程的工程類
  •  handler:飽和策略(拒絕策略)

阻塞隊列

  •  ArrayBlockingQueue

一個有限的BlockingQueue由數組支持。 這個隊列排列元素FIFO(先進先出)。 隊列的頭部是隊列中最長的元素。 隊列的尾部是隊列中最短時間的元素。 新元素插入隊列的尾部,隊列檢索操作獲取隊列頭部的元素。

返回值 方法名 描述
Boolean add(E e) 在插入此隊列的尾部,如果有可能立即這樣做不超過該隊列的容量,返回指定的元素 true成功時與拋出 IllegalStateException如果此隊列已滿。
void clear() 從這個隊列中原子地刪除所有的元素。
Boolean remove(Object o)  從該隊列中刪除指定元素的單個實例(如果存在)。 
int size() 返回此隊列中的元素數
E task() 檢索並刪除此隊列的頭,如有必要,等待元素可用。
Object[] toArray() 以適當的順序返回一個包含此隊列中所有元素的數組
<T> T[] toArray(T[] a)  以適當的順序返回包含此隊列中所有元素的數組; 返回的數組的運行時類型是指定數組的運行時類型
boolean offer(E e)  如果可以在不超過隊列容量的情況下立即將其指定的元素插入該隊列的尾部,則在成功時 false如果該隊列已滿,則返回 true 。
boolean offer(E e, long timeout, TimeUnit unit)  在該隊列的尾部插入指定的元素,等待指定的等待時間,以使空間在隊列已滿時變爲可用。 
E poll()

檢索並刪除此隊列的頭,如果此隊列爲空,則返回 null 。 

E poll(long timeout, TimeUnit unit)  檢索並刪除此隊列的頭,等待指定的等待時間(如有必要)使元素變爲可用。 
void put(E e)  在該隊列的尾部插入指定的元素,如果隊列已滿,則等待空間變爲可用。
int remainingCapacity()  返回此隊列可以理想地(在沒有內存或資源限制)的情況下接受而不阻止的附加元素數。 
Spliterator<E> spliterator()  返回此隊列中的元素Spliterator 。 
String toString()

返回此集合的字符串表示形式。   

package com.juc.pool.queue;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/30 23:50
 * @Description 商品
 */
public class Goods {
    /**
     * @Author zcm
     * @Email [email protected]
     * @date 2020/5/30 23:50
     * @Description:品牌
     */
    private String brand;
    /**
     * @Author zcm
     * @Email [email protected]
     * @date 2020/5/30 23:50
     * @Description:名稱
     */
    private String name;

    public Goods(String brand, String name) {
        this.brand = brand;
        this.name = name;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 生產者

package com.juc.pool.queue;

import java.util.concurrent.BlockingQueue;
/**
*@Author zcm
*@Email [email protected]
*@date 2020/5/31 9:44
*@Description:生產者不斷生產商品往隊列裏添加,隊列放滿了就不再生產,必須等消費者消費了,在生產商品
*/
public class ProducerQueue implements Runnable {
    private BlockingQueue<Goods> queue;

    public ProducerQueue(BlockingQueue<Goods> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            Goods goods = null;
            if (i % 2 == 0) {
                goods = new Goods("旺仔" + i, "小饅頭" + i);

            } else {
                goods = new Goods("娃娃哈" + i, "礦泉水" + i);
            }
            System.out.println("生產者開始生產商品" + goods.getBrand() + "--" + goods.getName());
            try {
                queue.put(goods);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消費者 

package com.juc.pool.queue;

import java.util.concurrent.BlockingQueue;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 9:44
 * @Description:消費者不斷消費隊列裏的商品,隊列沒有商品就不能被消費
 */
public class ConsumerQueue implements Runnable {
    private BlockingQueue<Goods> queue;


    public ConsumerQueue(BlockingQueue<Goods> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            Goods take = null;
            try {
                take = this.queue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消費者消費商品" + take.getBrand() + "--" + take.getName());
        }
    }
}
Main
package com.juc.pool.queue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 9:45
 * @Description:一個有限的blocking queue由數組支持。 這個隊列排列元素FIFO(先進先出)。
 * 隊列的頭部是隊列中最長的元素。 隊列的尾部是隊列中最短時間的元素。 新元素插入隊列的尾部,隊列檢索操作獲取隊列頭部的元素。
 */
public class ArrayBlockingQueueDemo {
    public static void main(String[] args) {
        BlockingQueue queue = new ArrayBlockingQueue(5);
        ProducerQueue producerQueue = new ProducerQueue(queue);
        ConsumerQueue consumerQueue = new ConsumerQueue(queue);
        new Thread(producerQueue).start();
        new Thread(consumerQueue).start();

    }
}
  •  LinkedBlockingQueue
  • 基於鏈接節點的可選限定的blockingqueue 。 這個隊列排列元素FIFO(先進先出)。 隊列的頭部是隊列中最長的元素。 隊列的尾部是隊列中最短時間的元素。 新元素插入隊列的尾部,隊列檢索操作獲取隊列頭部的元素。 鏈接隊列通常具有比基於陣列的隊列更高的吞吐量,但在大多數併發應用程序中的可預測性能較低。

package com.juc.pool.queue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 9:45
 * @Description:基於鏈接節點的可選限定的blocking queue 。 這個隊列排列元素FIFO(先進先出)。
 * 隊列的頭部是隊列中最長的元素。 隊列的尾部是隊列中最短時間的元素。 新元素插入隊列的尾部,隊列檢索操作獲取隊列頭部的元素。 鏈接隊列通常具有比基於陣列的隊列更高的吞吐量,但在大多數併發應用程序中的可預測性能較低。
 */
public class LinkedBlockingQueueDemo {
    public static void main(String[] args) {
        BlockingQueue queue = new LinkedBlockingQueue();
        ProducerQueue producerQueue = new ProducerQueue(queue);
        ConsumerQueue consumerQueue = new ConsumerQueue(queue);
        new Thread(producerQueue).start();
        new Thread(consumerQueue).start();

    }
}
  •  DelayQueue

一個無限制的BlockingQueueDelayed元素,其中元素只能在其延遲到期時才被使用。 隊列的Delayed元素,其延遲期滿後保存時間。 如果沒有延遲到期,那麼沒有頭, poll會返回null 。 當元素的getDelay(TimeUnit.NANOSECONDS)方法返回小於或等於零的值時,就會發生getDelay(TimeUnit.NANOSECONDS) 。 即使未使用的元素不能使用takepoll ,它們另外被視爲普通元素。 例如, size方法返回到期和未到期元素的計數。 此隊列不允許空元素。

package com.juc.pool.queue;

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class DelayedTask implements Delayed {

    private String name;

    private Long delayedTime;

    private TimeUnit delayedTimeUnit;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public DelayedTask(String name, Long delayedTime, TimeUnit delayedTimeUnit) {
        this.name = name;
        this.delayedTime = delayedTime;
        this.delayedTimeUnit = delayedTimeUnit;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(delayedTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) {
            return 1;
        } else if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)) {
            return -1;
        }
        return 0;
    }
}
package com.juc.pool.queue;

import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 18:04
 * @Description:一個無限制的blocking queue的Delayed元素,其中元素只能在其延遲到期時才被使用。 隊列的頭是Delayed元素,其延遲期滿後保存時間。 如果沒有延遲到期,那麼沒有頭, poll會返回null 。 當元素的getDelay(TimeUnit.NANOSECONDS)方法返回小於或等於零的值時,就會發生getDelay(TimeUnit.NANOSECONDS) 。 
 * 即使未使用的元素不能使用take或poll ,它們另外被視爲普通元素。 例如, size方法返回到期和未到期元素的計數。 此隊列不允許空元素。 
 */
public class DelayedQueueDemo {
    public static void main(String[] args) {

        DelayQueue<DelayedTask> queue = new DelayQueue<DelayedTask>();
        queue.put(new DelayedTask("1", 1000L, TimeUnit.MILLISECONDS));
        queue.put(new DelayedTask("2", 2000L, TimeUnit.MILLISECONDS));
        queue.put(new DelayedTask("3", 3000L, TimeUnit.MILLISECONDS));
        System.out.println("queue put done");

        while (!queue.isEmpty()) {
            DelayedTask task = null;
            try {
                task = queue.take();
                System.out.println(System.currentTimeMillis() + "---" + task.getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  •  PriorityBlockingQueue

一個無界的BlockingQueue使用與PriorityQueue類相同的排序規則,並提供阻塞檢索操作。 雖然這個隊列在邏輯上是無界的,但由於資源耗盡,嘗試的添加可能會失敗(導致OutOfMemoryError )。 這個類不允許null元素。 根據natural ordering的優先級隊列也不允許插入不可比較的對象(這樣做在ClassCastException )。 
該類及其迭代器實現CollectionIterator接口的所有可選方法。 方法iterator()中提供的迭代器不能保證以任何特定的順序遍歷PriorityBlockingQueue的元素。 如果需要排序遍歷,請考慮使用Arrays.sort(pq.toArray()) 。 此外,方法drainTo可以用於以優先級順序移除一些或所有元素並將它們放置在另一集合中。 這個類的操作不會保證等同優先級的元素的排序。 如果需要強制執行排序,可以定義自定義類或比較器,它們使用輔助鍵來破壞主優先級值的關係。 例如,這裏是一個適用於先進先出的打破破壞類似元素的課程。 要使用它,將插入一個new FIFOEntry(anEntry)而不是一個簡單的條目對象。

 

package com.juc.pool.queue;

public class PriorityTask implements Comparable<PriorityTask> {

    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int compareTo(PriorityTask o) {
        return this.id > o.id ? 1 : (this.id < o.id) ? -1 : 0;
    }

    @Override
    public String toString() {
        return "PriorityTask{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
package com.juc.pool.queue;

import java.util.concurrent.PriorityBlockingQueue;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 18:12
 * @Description:一個無界的blocking queue使用與PriorityQueue類相同的排序規則,並提供阻塞檢索操作。
 * 雖然這個隊列在邏輯上是無界的,但由於資源耗盡,嘗試的添加可能會失敗(導致OutOfMemoryError )。
 * 這個類不允許null元素。 根據natural ordering的優先級隊列也不允許插入不可比較的對象(這樣做在ClassCastException )。
 */
public class PriorityBlockingQueueDemo {
    public static void main(String[] args) {

        PriorityBlockingQueue<PriorityTask> queue = new PriorityBlockingQueue<>();
        for (int i = 1; i <=4; i++) {
            PriorityTask task = new PriorityTask();
            task.setId(i);
            task.setName("Id爲" + task.getId());
            if (i % 2 == 0) {
                task.setId(i + 2);
                task.setName("Id爲" + task.getId());
            }
            queue.add(task);
        }
        System.out.println("容器:" + queue);
        try {
            System.out.println(queue.take().getId());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("容器:"+queue);
    }
}
  •  SynchronousQueue

A blocking queue其中每個插入操作必須等待另一個線程相應的刪除操作,反之亦然。 同步隊列沒有任何內部容量,甚至沒有一個容量。 你不能peek在同步隊列,因爲一個元素,當您嘗試刪除它才存在; 您無法插入元素(使用任何方法),除非另有線程正在嘗試刪除它; 你不能迭代,因爲沒有什麼可以迭代。 隊列的頭部是第一個排隊的插入線程嘗試添加到隊列中的元素; 如果沒有這樣排隊的線程,那麼沒有元素可用於刪除,並且poll()將返回null 。 爲了其他Collection方法(例如contains )的目的, SynchronousQueue充當空集合。 此隊列不允許null元素。 
同步隊列類似於CSP和Ada中使用的會合通道。 它們非常適用於切換設計,其中運行在一個線程中的對象必須與在另一個線程中運行的對象同步,以便交付一些信息,事件或任務。 

此類支持可選的公平策略,用於訂購等待的生產者和消費者線程。 默認情況下,此訂單不能保證。 然而,以公平設置爲true的隊列以FIFO順序授予線程訪問權限。

package com.juc.pool.queue;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 18:31
 * @Description:A blocking queue其中每個插入操作必須等待另一個線程相應的刪除操作,反之亦然。
 * 同步隊列沒有任何內部容量,甚至沒有一個容量。 你不能peek在同步隊列,因爲一個元素,當您嘗試刪除它才存在;
 * 您無法插入元素(使用任何方法),除非另有線程正在嘗試刪除它;你不能迭代,因爲沒有什麼可以迭代。
 * 隊列的頭部是第一個排隊的插入線程嘗試添加到隊列中的元素; 如果沒有這樣排隊的線程,那麼沒有元素可用於刪除,並且poll()將返回null 。
 * 爲了其他Collection方法(例如contains )的目的, SynchronousQueue充當空集合。 此隊列不允許null元素。
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<Goods> queue = new SynchronousQueue<Goods>();
        ProducerQueue producerQueue = new ProducerQueue(queue);
        ConsumerQueue consumerQueue = new ConsumerQueue(queue);
        new Thread(producerQueue).start();
        new Thread(consumerQueue).start();
    }
}

拒絕策略

  •  ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。【默認第一個】
  •  ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
  •  ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
  •  ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

Execute方法執行邏輯

  1. 如果當前運行的線程少於corePoolSize,則會創建新的線程執行新的任務。
  2. 如果運行的線程個數等於或者大於corePoolSize,則會將提交的任務放到阻塞隊列WorkQueue中。
  3. 如果當前WorkQueue隊列已滿,則會創建新的線程來執行任務。
  4. 如果線程個數已經超過maximumPoolSize,則會使用飽和策略RejectedExecutionHander來處理。

submit方法 

submit是基方法Executor.execute(Runnable)的延伸,通過創建並返回一個Future類對象可用於取消執行和/或等待完成。

package com.juc.pool.queue;

import java.util.concurrent.Callable;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 19:37
 * @Description: Callable
 */
public class Task implements Callable<String> {

    @Override
    public String call() throws Exception {
        return Thread.currentThread().getName() + "is running";
    }
}
package com.juc.pool.queue;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import static java.util.concurrent.Executors.*;
/**
*@Author zcm
*@Email [email protected]
*@date 2020/5/31 19:44
*@Description:submit提交值返回任務以執行,並返回代表任務待處理結果的Future。 未來的get方法將在成功完成後返回任務的結果。
 *  如果您想立即阻止等待任務,您可以使用result = exec.submit(aCallable).get();格式的result = exec.submit(aCallable).get(); 
 * 注意: Executors類包括一組方法,可以將一些其他常見的類似對象的對象,例如PrivilegedAction轉換爲Callable表單,以便它們可以提交。 
*/
public class TestTask {
    public static void main(String[] args) {
        ExecutorService executorService = newFixedThreadPool(5);
        for (int i = 1; i <= 10; i++) {
            Future<String> future = executorService.submit(new Task());
            try {
                Object x = future.get();
                System.out.println(x);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }

        }

    }


}

線程池的關閉

關閉線程池,可以通過shutdown和shutdownNow兩個方法。

原理:遍歷線程池中的所有線程,然後一次中斷。

  1. shutdownNow首先將線程池的狀態設置爲Stop然後嘗試停止所有正在執行和未執行任務的線程,並返回等待執行任務的列表。
  2. shutdown只是將線程池的狀態設置爲SHUTDOWN狀態,然後中斷所有沒有執行任務的線程。

自定義適合自己的線程池 

package com.juc.pool;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 19:54
 * @Description:自定義個線程池
 */
public class ThreadPoolDemo {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("打印個隨機數:"+Math.random());;
            }
        });
    }
}

 

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