Callable,阻塞隊列,線程池問題

一.說說Java建立多線程的方法java

  1. 經過繼承Thread類實現run方法數據庫

     2. 經過實現Runnable接口數組

  3. 經過實現Callable接口緩存

  4. 經過線程池獲取多線程

 

二. 能夠寫一個Callable的案例嗎?如何調用Callable接口併發

/*是一個帶返回值的多線程類,若是須要有線程返回的結果,就須要使用此類*/
class MyThread implements Callable<Integer> {

    @Override
    public Integer call()  {
        return 1000;
    }
}
public static void main(String[] args) throws Exception{

        /*Thread 構造方法傳入 Runnable , FutureTask 構造方法傳入 Callable , 
        FutureTask 繼承於 RunnableFuture 繼承於 Runnable
        因此 Thread經過傳入FutureTask即Runnable的實現類來啓動Callable線程,
        透傳思想,傳接口,不傳具體的實現類,能夠保證靈活性,(適配器模式)
       */
        FutureTask<Integer> ft = new FutureTask(new MyThread());// 一個是main線程,一個是t1線程
        new Thread(ft,"t1").start();


        int res1 = 1000;
        // 獲取t1線程中的返回值,建議放在最後
        // 若是沒有計算完成就取值,會致使整個程序阻塞,直到t1執行完成
        int res2 = ft.get();
        //自旋鎖,若是t1線程沒有計算完成,就等待
        while (!ft.isDone()){

        }
        System.out.println(Thread.currentThread().getName() + "\t"+(res1+res2));
    }

三. 請你談談對阻塞隊列的理解,爲何要是用阻塞隊列,它有哪些具體的實現,各有什麼特色?ide

在多線程的環境下,所謂阻塞,在某些狀況下會掛起線程,一旦條件知足,被掛起的線程又被喚醒.咱們不須要關心spa

何時須要阻塞線程,何時須要喚醒線程,一切都有BlockingQueue自動調度實現線程

 它有7個實現類
* 1.ArrayBlockingQueue 由數組構成的有序阻塞隊列
* 2.LinkedBlockingQueue 由鏈表構成的有序阻塞隊列
* 3.PriorityBlockingQueue 支持優先級排序的無界阻塞隊列
* 4.DelayQueue 使用優先級隊列的延遲無阻塞隊列
* 5.SynchronousQueue 不存儲元素的阻塞隊列,也即單元素隊列
* 6.LinkedTransferQueue 由鏈表組成的無界阻塞隊列
* 7.LinkedBlockingQueue 由鏈表構成的雙向阻塞隊列

1,2,5用的最多code

BlockingQueue的使用方法

1.會拋出異常

public static void show01(int n){
        /*隊列中的數據錯誤,會拋出異常*/
        /*add     隊列滿了,再繼續插入會拋出java.lang.IllegalStateException: Queue full異常*/
        /*remove  從隊列中移除一個元素,並返回該元素,若是隊列爲空,就拋出java.util.NoSuchElementException異常*/
        /*element 返回隊列的首個元素,若是隊列爲空,拋出java.util.NoSuchElementException異常*/
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(n);
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
//        System.out.println(blockingQueue.add("x"));
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        //System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.element());//
    }

2.不會拋出異常

public static void show02(int n){
        /*隊列數據錯誤,不會拋出異常*/
        /*offer 添加元素,若是隊列滿了,再繼續插入元素,會返回false*/
        /*poll 獲取元素,若是隊列爲空,再繼續獲取元素,會返回null*/
        /*peek 獲取隊列的首元素,若是隊列爲空,返回null*/
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(n);
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
//        System.out.println(blockingQueue.offer("x"));
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
//        System.out.println(blockingQueue.poll());

        System.out.println(blockingQueue.peek());
    }

3.隊列阻塞

public static void show03(int n){
        /*隊列數據異常,不會拋出異常,沒有返回值*/
        /*put 若是隊列已滿,在插入元素,隊列會阻塞*/
        /*take 若是隊列爲空,在獲取元素,隊列會阻塞*/
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(n);
        try {
            blockingQueue.put("a");
            blockingQueue.put("b");
            blockingQueue.put("c");
          // blockingQueue.put("x");
            System.out.println("-------------------");
            blockingQueue.take();
            blockingQueue.take();
            blockingQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

4.隊列超時 (實際用的較多)

public static void show04(int n){
        /*隊列數據異常,不會拋出異常,返回true/false值*/
        /*offer 傳入值,等待時間,等待單位,往隊列中存放元素,當隊列中的元素已滿,繼續等待,若是超過給定時間,那麼就會丟棄元素*/
        /*poll 等待時間,等待單位,從隊列中獲取元素,當隊列中的元素爲空,繼續等待,若是超過給定時間,那麼就不會獲取*/
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(n);
        try {
            System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
            System.out.println(blockingQueue.offer("b", 2L, TimeUnit.SECONDS));
            System.out.println(blockingQueue.offer("c", 2L, TimeUnit.SECONDS));
            System.out.println(blockingQueue.offer("x", 2L, TimeUnit.SECONDS));

            blockingQueue.poll(2L,TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 public static void main(String[] args) {
        show01(3); //    異常
        show02(3); //   沒有異常
        show03(3); //   阻塞
        show04(3); // 超時等待
    }

四.請你談談對線程池的理解,爲何要使用線程池,如何使用線程池?

介紹:線程池的主要是控制運行的線程的數量,若是處理過程當中將任務加入隊列,而後在線程建立的時候啓動這些任務.若是線程數量超過了最大數量的線程將排隊等候,等待其餘的線程執行完畢,再從隊列中取出任務來執行.

優勢:

  1.下降資源消耗,經過複用已建立的線程下降線程建立和銷燬形成的消耗

  2.提升響應速度,當任務達到時,任務能夠不須要等待線程建立就能夠馬上執行

  3.提升線程的可管理性,線程屬於稀缺資源,若是無限制的建立,不只會消耗系統資源,還會下降系統的穩定性

     使用線程池能夠進行統一的分配,調優和監控

一句話總結:線程複用,控制最大併發數,管理線程

 public static void show01(ExecutorService es){
        // 建立1個線程池,裏面有5個固定的線程
        /*1. 建立一個定長的線程池,可控制線程的最大併發數,超出的線程會在隊列中等待
        * 2. newFixedThreadPool建立的線程池corePoolSize和maxPoolSize的值事相等的,使用的是LinkedBlockingQueue
        * */

        es = Executors.newFixedThreadPool(5);

        try{
            // 有10個任務等待被執行
            for (int i = 1; i <= 10; i++) {
                es.execute(()->{
                    System.out.println(Thread.currentThread().getName() +"\t 執行任務");
                });
            }
        }catch (Exception e){
            e.getStackTrace();
        }finally {
            es.shutdown();//關閉線程池
        }
    }
 public static void show02(ExecutorService es){
        // 建立1個線程池,裏面有1個固定的線程
        /*
        * 1.建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務,保證全部的任務按照指定的順序執行
        * 2.newSingleThreadExecutor將corePoolSize和maxPoolSize的值都設置爲1,使用的是LinkedBlockingQueue
        * */
        es = Executors.newSingleThreadExecutor();

        try{
            for (int i = 1; i <= 10; i++) {
                es.execute(()->{
                    System.out.println(Thread.currentThread().getName() +"\t 執行任務");
                });
            }
        }catch (Exception e){
            e.getStackTrace();
        }finally {
            es.shutdown();//關閉線程池
        }
    }
public static void show03(ExecutorService es){
        // 建立1個線程池,裏面無固定個線程
        /*
        * 1.建立一個可緩存的線程池,若是線程池長度超過處理須要,可靈活回收空閒線程,若無可回收,則建立新線程
        * 2.newCachedThreadPool將corePoolSize的值設置爲0,maxPoolSize的值設置爲Integer.MAX_VALUE,使用
        *  的是synchronousQueue,也就是說來了任務就建立線程運行,當線程空閒超過60秒,就銷燬線程
        *
        * */
        es = Executors.newCachedThreadPool();
        try{
            for (int i = 1; i <= 10; i++) {
                es.execute(()->{
                    System.out.println(Thread.currentThread().getName() +"\t 執行任務");
                });

            }
        }catch (Exception e){
            e.getStackTrace();
        }finally {
            es.shutdown();//關閉線程池
        }
    }
public static void main(String[] args) {
        ExecutorService es = null;
        show01(es);
        show02(es);
        show03(es);
    }

 五. 請你談一談線程池的七大/五大參數分別表明什麼意思

int corePoolSize                      線程池中常駐核心線程數
    * 1.在建立了線程池後,當有請求任務來了以後,就會安排線程池中的線程去執行請求的任務,相似於銀行當值的窗口
    * 2.當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中
int maximumPoolSize                   線程池同時可以容納同時執行的最大線程數,此值必須大於等於1
long keepAliveTime                    多餘的空閒線程的存活時間
      * 當前線程池數量超過corePoolSize時,空閒時間達到keepAliveTime值時,多餘線程會被銷燬直到只剩下corePoolSize個線程爲止
      * 默認狀況下,只有當線程池中的線程數大於corePoolSize時候keepAliveTime纔會起做用,直到線程池中的線程數不大於corePoolSize
      *
TimeUnit unit                         keepAliveTime的單位
BlockingQueue<Runnable> workQueue     任務隊列,被提交但還沒有被執行的任務
ThreadFactory threadFactory           表示生成線程池中工做線程的線程工廠,用於建立線程,通常用默認的值便可
RejectedExecutionHandler handler      拒絕策略,表示當隊列滿了而且工做線程大於等於線程池的最大線程數(maximumPoolSize)如何來拒絕請求執行的Runnable策略

六.請你談一談線程池的底層工做原理

  1.在建立了線程池後,等待提交過來的任務請求

  2.當調用execute方法添加一個任務請求時,線程池會作出以下判斷:

    2.1 若是正在運行的線程數量小於corePoolSize,那麼會立刻建立這個線程並執行這個任務

    2.2 若是此時正在運行的線程數量大於或等於corePoolSize,那麼會將這個這個任務放進阻塞隊列中等待

    2.3 若是此時阻塞隊列滿了且正在運行的線程數量小於maximumPoolSize,那麼會建立非核心線程來當即執行這些任務

    2.4 若是此時阻塞隊列滿了且正在運行的線程數大於或等於maximumPoolSize.那麼線程池會啓用飽和拒絕策略來拒絕新的請求

  3.當一個線程完成任務時,會從阻塞隊列中取出下一個任務來執行

  4.此時當一個線程空閒並超過必定的時間keepAliveTime,此時線程池會判斷,若是當前運行的線程數量大於corePoolSize,那麼這些線程將會被終止.

     因此線程池的全部任務完成後,最終會收縮到corePoolSize的大小.

 舉列子:銀行辦理業務,週六招商銀行,一共有5個櫃面,只開放了2個櫃檯辦理業務,此時2就表明corePoolSize,5個櫃檯表明了maximumPoolSize,只有2個員工在幹活,

    其餘的3個櫃檯都是空閒的當有人來進去辦業務,若是來的人少於2人能夠當即辦理業務,無需等待,當來了5我的辦理業務,前面的1,2號沒有辦完,此時3,4,5號就

    會進入候客區等待.也就是線程池中的阻塞隊列中等待被執行.若是此時來銀行辦理業務的任實在太多,來了7,8,9,候客區也滿了(BlockingQueue滿了),此時銀行

    行長就會開放剩餘的櫃檯來處理業務(非核心線程打開,此時線程數達到最大值),打電話給另外3個櫃員來加班,此時櫃檯所有開放,也知足不了新的客戶需求,那麼

    最後銀行行長就會暫時關閉這個網點(拒絕策略拒絕新的請求),先來辦理行內的業務.當解決了一個任務時,就會叫下一個號來辦理業務(從阻塞隊列中獲取待

    解決的任務).若是過了一個小時(keepAliveTime保留活躍時間),銀行的流量降低了,此時,加班的3我的能夠先回去(原本也不應他們上班),銀行從新迴歸到初始狀

    態.(這麼說,小夥伴們懂了嗎~)

七. 請你談一談線程池的拒絕策略

  此時線程池中的阻塞隊列已經被塞滿,再也放不進新的任務,同時線程數也達到了maximumPoolSize,沒法爲新的請求服務,此時JDK有4種拒絕策略

1.AbortPolicy(默認)    直接拋出RejectedExecutionException異常阻止系統正常運行
2.CallersRunsPolicy    "調用者模式"一種調節機制,該策略既不會拋棄任務,也不會拋出異常,而是將某些任務回退到調用者,從而下降新任務的流量
3.DiscardOldestPolicy  拋棄隊列中等待最久的任務,而後把當前任務加入隊列中嘗試再次提交當前任務
4.DiscardPolicy        直接丟棄任務,不予處理也不拋出異常.若是容許任務丟失,這是最好的一種方案

  那此時能夠說下在工做中用了哪種線程池?(此處有坑)

  JDK提供的線程池一概不能說,標準答案: 根據業務的須要,自定義ThreadPoolExectuor來建立線程池.

public static void show04(ExecutorService es){
       // 核心線程數爲2,最大線程數爲5,非核心線程的最大運行時間是1秒
       // 阻塞隊列中一共有3個等待的任務,使用默認的線程生成策略,同時開啓
       // 調用者模式的拒絕策略
       es = new ThreadPoolExecutor(
                2,5,1L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());
        try{
            for (int i = 1; i <= 10; i++) { // 開啓10個請求,最大線程數量是8,
                es.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"\t辦理業務");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            es.shutdown();
        }
    }

運行結果:

AbortPolicy,直接拋出異常

 

 

CallersRunsPolicy 不會拋出異常,返回給方法的調用者,也就是main線程

 

 

DiscardOldestPolicy  拋棄隊列中等待最久的任務

 

 

DiscardPolicy 直接拋棄隊列中多餘的任務,若是業務容許,此拒絕策略效率最高

 

 

八. 請你談一談,如何在實際的生產業務中肯定線程池中參數的配置

此時要根據實際作的系統進行分類

  1.CPU密集型

/*指的是該任務須要大量的運算,沒有阻塞,CPU一直全速運行,CPU密集任務只有真正的多核心CPU才能獲得加速(經過多線程)而在單核CPU上
不管開幾個模擬多線程的任務都不可能獲得加速,由於CPU的運算能力有限. 1.CPU密集型的任務儘量少配線程數量 2.公式:線程數 = CPU核心數 + 1個線程的線程池(8核就開啓8個線程,12核開啓12個線程,儘可能減小切換) 3.System.out.println(Runtime.getRuntime().availableProcessors());//查看CPU的核心數
*/

  2.I/O密集型

/* 1.I/O密集型並非在一直執行任務,應該配置儘量多的線程,線程數 = CPU核心數 * 2
   2.I/O密集型即該任務須要大量的I/O操做,業務系統中大量的阻塞(從數據庫,緩存獲取數據,修改數據...)
      因此在I/O密集型的場景中,使用多線程能夠大大加速程序運行,即便在單核CPU上,這種加速也能夠利用了被浪費掉的阻塞時間
      由於大部分線程都會阻塞,須要配置大量的線程數 
      線程數 = CPU核心數 / 1-阻塞係數 阻塞係數在0.8~0.9之間
      (例如 8核心CPU 8 / 1 - 0.9 = 80,若是是I/O密集型的操做,須要配置80個線程數) 
*/
相關文章
相關標籤/搜索