一.說說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個線程數) */