Java多線程八股文背誦版V0.2
本文轉載自微信公眾號「后端技術小牛說」,作者后端技術小牛說。轉載本文請聯(lián)系后端技術小牛說公眾號。
寫在最前面
最近有收到讀者的一些反饋,感謝了我開發(fā)了網(wǎng)站interviewtop.top,對他們面試產(chǎn)生了巨大幫助,更有讀者給我發(fā)了小紅包贊助我。當然啦,我也知道大家還是以學生群體為主,紅包和贊助免了哈!
我之前還是學生身份,分別租了阿里云騰訊云等等云服務器的9.9元學生套餐,利用各個廠的學生福利云服務器負載均衡做服務器后端,這幾個月,由于已經(jīng)成為社會人了,當然沒這點福利了(9.9同等配置社會人現(xiàn)在得近200,所以還在上學的同學有需求這個羊毛可以薅一把)。而且隨著大家使用頻率的增高,配置要求當然也不一定打的住了。
除此之外,interviewtop網(wǎng)站也需要大家的幫助和支持!如果大家有面經(jīng),希望大家能貢獻一下:
在全部和對應題庫下點擊搜索
搜索自己的面試題
點擊這題我面試見過,貢獻一下自己的面試經(jīng)歷
如果有些題題庫沒收錄,歡迎大家添加小牛微信,小牛后臺更新上!
簡述java內(nèi)存模型(JMM)
java內(nèi)存模型定義了程序中各種變量的訪問規(guī)則。其規(guī)定所有變量都存儲在主內(nèi)存,線程均有自己的工作內(nèi)存。工作內(nèi)存中保存被該線程使用的變量的主內(nèi)存副本,線程對變量的所有操作都必須在工作空間進行,不能直接讀寫主內(nèi)存數(shù)據(jù)。操作完成后,線程的工作內(nèi)存通過緩存一致性協(xié)議將操作完的數(shù)據(jù)刷回主存。
簡述as-if-serial
編譯器等會對原始的程序進行指令重排序和優(yōu)化。但不管怎么重排序,其結果和用戶原始程序輸出預定結果一致。
簡述happens-before八大原則
程序次序規(guī)則:一個線程內(nèi)寫在前面的操作先行發(fā)生于后面的。
鎖定規(guī)則:unlock 操作先行發(fā)生于后面對同一個鎖的 lock 操作。
volatile 規(guī)則:對 volatile 變量的寫操作先行發(fā)生于后面的讀操作。
線程啟動規(guī)則:線程的 start 方法先行發(fā)生于線程的每個動作。
線程中斷規(guī)則:對線程interrupt()方法的調用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生。
線程終止規(guī)則:線程中所有操作先行發(fā)生于對線程的終止檢測。
對象終結規(guī)則:對象的初始化先行發(fā)生于 finalize 方法。
傳遞性規(guī)則:如果操作 A 先行發(fā)生于操作 B,操作 B 先行發(fā)生于操作 C,那么操作 A 先行發(fā)生于操作 C
as-if-serial 和 happens-before 的區(qū)別
as-if-serial 保證單線程程序的執(zhí)行結果不變,happens-before 保證正確同步的多線程程序的執(zhí)行結果不變。
簡述原子性操作
一個操作或者多個操作,要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行,這就是原子性操作。
簡述線程的可見性
可見性指當一個線程修改了共享變量時,其他線程能夠立即得知修改。volatile,synchronized,final都能保證可見性。
簡述有序性
即雖然多線程存在并發(fā)和指令優(yōu)化等操作,在本線程內(nèi)觀察該線程的所有執(zhí)行操作是有序的。
簡述java中volatile關鍵字作用
保證變量對所有線程的可見性。當一條線程修改了變量值,新值對于其他線程來說是立即可以得知的。
禁止指令重排序優(yōu)化。使用 volatile 變量進行寫操作,匯編指令帶有 lock 前綴,相當于一個內(nèi)存屏障,編譯器不會將后面的指令重排到內(nèi)存屏障之前。
java線程的實現(xiàn)方式
- 實現(xiàn)Runnable接口
- 繼承Thread類。
- 實現(xiàn)Callable接口
簡述java線程的狀態(tài)
線程狀態(tài)有New, RUNNABLE, BLOCK, WAITING, TIMED_WAITING, THERMINATED NEW:新建狀態(tài),線程被創(chuàng)建且未啟動,此時還未調用 start 方法。
RUNNABLE: 運行狀態(tài)。其表示線程正在JVM中執(zhí)行,但是這個執(zhí)行,不一定真的在跑,也可能在排隊等CPU。
BLOCKED:阻塞狀態(tài)。線程等待獲取鎖,鎖還沒獲得。
WAITING: 等待狀態(tài)。線程內(nèi)run方法運行完語句Object.wait()/Thread.join()進入該狀態(tài)。
TIMED_WAITING:限期等待。在一定時間之后跳出狀態(tài)。調用Thread.sleep(long) Object.wait(long) Thread.join(long)進入狀態(tài)。其中這些參數(shù)代表等待的時間。
TERMINATED:結束狀態(tài)。線程調用完run方法進入該狀態(tài)。
簡述線程通信的方式
- volatile 關鍵詞修飾變量,保證所有線程對變量訪問的可見性。
- synchronized關鍵詞。確保多個線程在同一時刻只能有一個處于方法或同步塊中。
- wait/notify方法
- IO通信
簡述線程池
沒有線程池的情況下,多次創(chuàng)建,銷毀線程開銷比較大。如果在開辟的線程執(zhí)行完當前任務后執(zhí)行接下來任務,復用已創(chuàng)建的線程,降低開銷、控制最大并發(fā)數(shù)。
線程池創(chuàng)建線程時,會將線程封裝成工作線程 Worker,Worker 在執(zhí)行完任務后還會循環(huán)獲取工作隊列中的任務來執(zhí)行。
將任務派發(fā)給線程池時,會出現(xiàn)以下幾種情況
核心線程池未滿,創(chuàng)建一個新的線程執(zhí)行任務。
如果核心線程池已滿,工作隊列未滿,將線程存儲在工作隊列。
如果工作隊列已滿,線程數(shù)小于最大線程數(shù)就創(chuàng)建一個新線程處理任務。
如果超過大小線程數(shù),按照拒絕策略來處理任務。
線程池參數(shù)
- corePoolSize:常駐核心線程數(shù)。超過該值后如果線程空閑會被銷毀。
- maximumPoolSize:線程池能夠容納同時執(zhí)行的線程最大數(shù)。
- keepAliveTime:線程空閑時間,線程空閑時間達到該值后會被銷毀,直到只剩下 corePoolSize 個線程為止,避免浪費內(nèi)存資源。
- workQueue:工作隊列。
- threadFactory:線程工廠,用來生產(chǎn)一組相同任務的線程。
- handler:拒絕策略。有以下幾種拒絕策略:
- AbortPolicy:丟棄任務并拋出異常
- CallerRunsPolicy:重新嘗試提交該任務
- DiscardOldestPolicy 拋棄隊列里等待最久的任務并把當前任務加入隊列
- DiscardPolicy 表示直接拋棄當前任務但不拋出異常。
線程池創(chuàng)建方法
newFixedThreadPool,創(chuàng)建固定大小的線程池。
newSingleThreadExecutor,使用單線程線程池。
newCachedThreadPool,maximumPoolSize 設置為 Integer 最大值,工作完成后會回收工作線程
newScheduledThreadPool:支持定期及周期性任務執(zhí)行,不回收工作線程。
newWorkStealingPool:一個擁有多個任務隊列的線程池。
簡述Executor框架
Executor框架目的是將任務提交和任務如何運行分離開來的機制。用戶不再需要從代碼層考慮設計任務的提交運行,只需要調用Executor框架實現(xiàn)類的Execute方法就可以提交任務。產(chǎn)生線程池的函數(shù)ThreadPoolExecutor也是Executor的具體實現(xiàn)類。
簡述Executor的繼承關系
- Executor:一個接口,其定義了一個接收Runnable對象的方法executor,該方法接收一個Runable實例執(zhí)行這個任務。
- ExecutorService:Executor的子類接口,其定義了一個接收Callable對象的方法,返回 Future 對象,同時提供execute方法。
- ScheduledExecutorService:ExecutorService的子類接口,支持定期執(zhí)行任務。
- AbstractExecutorService:抽象類,提供 ExecutorService 執(zhí)行方法的默認實現(xiàn)。
- Executors:實現(xiàn)ExecutorService接口的靜態(tài)工廠類,提供了一系列工廠方法用于創(chuàng)建線程池。
- ThreadPoolExecutor:繼承AbstractExecutorService,用于創(chuàng)建線程池。
- ForkJoinPool: 繼承AbstractExecutorService,F(xiàn)ork 將大任務分叉為多個小任務,然后讓小任務執(zhí)行,Join 是獲得小任務的結果,類似于map reduce。
- ThreadPoolExecutor:繼承ThreadPoolExecutor,實現(xiàn)ScheduledExecutorService,用于創(chuàng)建帶定時任務的線程池。
簡述線程池的狀態(tài)
- Running:能接受新提交的任務,也可以處理阻塞隊列的任務。
- Shutdown:不再接受新提交的任務,但可以處理存量任務,線程池處于running時調用shutdown方法,會進入該狀態(tài)。
- Stop:不接受新任務,不處理存量任務,調用shutdownnow進入該狀態(tài)。
- Tidying:所有任務已經(jīng)終止了,worker_count(有效線程數(shù))為0。
- Terminated:線程池徹底終止。在tidying模式下調用terminated方法會進入該狀態(tài)。
簡述阻塞隊列
阻塞隊列是生產(chǎn)者消費者的實現(xiàn)具體組件之一。當阻塞隊列為空時,從隊列中獲取元素的操作將會被阻塞,當阻塞隊列滿了,往隊列添加元素的操作將會被阻塞。具體實現(xiàn)有:
- ArrayBlockingQueue:底層是由數(shù)組組成的有界阻塞隊列。
- LinkedBlockingQueue:底層是由鏈表組成的有界阻塞隊列。
- PriorityBlockingQueue:阻塞優(yōu)先隊列。
- DelayQueue:創(chuàng)建元素時可以指定多久才能從隊列中獲取當前元素
- SynchronousQueue:不存儲元素的阻塞隊列,每一個存儲必須等待一個取出操作
- LinkedTransferQueue:與LinkedBlockingQueue相比多一個transfer方法,即如果當前有消費者正等待接收元素,可以把生產(chǎn)者傳入的元素立刻傳輸給消費者。
- LinkedBlockingDeque:雙向阻塞隊列。
談一談ThreadLocal
ThreadLocal 是線程共享變量。ThreadLoacl 有一個靜態(tài)內(nèi)部類 ThreadLocalMap,其 Key 是 ThreadLocal 對象,值是 Entry 對象,ThreadLocalMap是每個線程私有的。
- set 給ThreadLocalMap設置值。
- get 獲取ThreadLocalMap。
- remove 刪除ThreadLocalMap類型的對象。
存在的問題
- 對于線程池,由于線程池會重用 Thread 對象,因此與 Thread 綁定的 ThreadLocal 也會被重用,造成一系列問題。
- 內(nèi)存泄漏。由于 ThreadLocal 是弱引用,但 Entry 的 value 是強引用,因此當 ThreadLocal 被垃圾回收后,value 依舊不會被釋放,產(chǎn)生內(nèi)存泄漏。
聊聊你對java并發(fā)包下unsafe類的理解
對于 Java 語言,沒有直接的指針組件,一般也不能使用偏移量對某塊內(nèi)存進行操作。這些操作相對來講是安全(safe)的。
Java 有個類叫 Unsafe 類,這個類類使 Java 擁有了像 C 語言的指針一樣操作內(nèi)存空間的能力,同時也帶來了指針的問題。這個類可以說是 Java 并發(fā)開發(fā)的基礎。
JAVA中的樂觀鎖與CAS算法
對于樂觀鎖,開發(fā)者認為數(shù)據(jù)發(fā)送時發(fā)生并發(fā)沖突的概率不大,所以讀操作前不上鎖。
到了寫操作時才會進行判斷,數(shù)據(jù)在此期間是否被其他線程修改。如果發(fā)生修改,那就返回寫入失敗;如果沒有被修改,那就執(zhí)行修改操作,返回修改成功。
樂觀鎖一般都采用 Compare And Swap(CAS)算法進行實現(xiàn)。顧名思義,該算法涉及到了兩個操作,比較(Compare)和交換(Swap)。
CAS 算法的思路如下:
- 該算法認為不同線程對變量的操作時產(chǎn)生競爭的情況比較少。
- 該算法的核心是對當前讀取變量值 E 和內(nèi)存中的變量舊值 V 進行比較。
- 如果相等,就代表其他線程沒有對該變量進行修改,就將變量值更新為新值 N。
- 如果不等,就認為在讀取值 E 到比較階段,有其他線程對變量進行過修改,不進行任何操作。
ABA問題及解決方法簡述
CAS 算法是基于值來做比較的,如果當前有兩個線程,一個線程將變量值從 A 改為 B ,再由 B 改回為 A ,當前線程開始執(zhí)行 CAS 算法時,就很容易認為值沒有變化,誤認為讀取數(shù)據(jù)到執(zhí)行 CAS 算法的期間,沒有線程修改過數(shù)據(jù)。
juc 包提供了一個 AtomicStampedReference,即在原始的版本下加入版本號戳,解決 ABA 問題。
簡述常見的Atomic類
在很多時候,我們需要的僅僅是一個簡單的、高效的、線程安全的++或者--方案,使用synchronized關鍵字和lock固然可以實現(xiàn),但代價比較大,此時用原子類更加方便?;緮?shù)據(jù)類型的原子類有:
- AtomicInteger 原子更新整形
- AtomicLong 原子更新長整型
- AtomicBoolean 原子更新布爾類型
Atomic數(shù)組類型有:
- AtomicIntegerArray 原子更新整形數(shù)組里的元素
- AtomicLongArray 原子更新長整型數(shù)組里的元素
- AtomicReferenceArray 原子更新引用類型數(shù)組里的元素。
Atomic引用類型有
- AtomicReference 原子更新引用類型
- AtomicMarkableReference 原子更新帶有標記位的引用類型,可以綁定一個 boolean 標記
- AtomicStampedReference 原子更新帶有版本號的引用類型
FieldUpdater類型:
- AtomicIntegerFieldUpdater 原子更新整形字段的更新器
- AtomicLongFieldUpdater 原子更新長整形字段的更新器
- AtomicReferenceFieldUpdater 原子更新引用類型字段的更新器
簡述Atomic類基本實現(xiàn)原理
以AtomicIntger 為例:方法getAndIncrement:以原子方式將當前的值加1,具體實現(xiàn)為:
- 在 for 死循環(huán)中取得 AtomicInteger 里存儲的數(shù)值
- 對 AtomicInteger 當前的值加 1
- 調用 compareAndSet 方法進行原子更新
- 先檢查當前數(shù)值是否等于 expect
- 如果等于則說明當前值沒有被其他線程修改,則將值更新為 next,
- 如果不是會更新失敗返回 false,程序會進入 for 循環(huán)重新進行 compareAndSet 操作。
簡述CountDownLatch
countDownLatch這個類使一個線程等待其他線程各自執(zhí)行完畢后再執(zhí)行。是通過一個計數(shù)器來實現(xiàn)的,計數(shù)器的初始值是線程的數(shù)量。每當一個線程執(zhí)行完畢后,調用countDown方法,計數(shù)器的值就減1,當計數(shù)器的值為0時,表示所有線程都執(zhí)行完畢,然后在等待的線程就可以恢復工作了。只能一次性使用,不能reset。
簡述CyclicBarrier
CyclicBarrier 主要功能和countDownLatch類似,也是通過一個計數(shù)器,使一個線程等待其他線程各自執(zhí)行完畢后再執(zhí)行。但是其可以重復使用(reset)。
簡述Semaphore
Semaphore即信號量。Semaphore 的構造方法參數(shù)接收一個 int 值,設置一個計數(shù)器,表示可用的許可數(shù)量即最大并發(fā)數(shù)。使用 acquire 方法獲得一個許可證,計數(shù)器減一,使用 release 方法歸還許可,計數(shù)器加一。如果此時計數(shù)器值為0,線程進入休眠。
簡述Exchanger
Exchanger類可用于兩個線程之間交換信息??珊唵蔚貙xchanger對象理解為一個包含兩個格子的容器,通過exchanger方法可以向兩個格子中填充信息。線程通過exchange 方法交換數(shù)據(jù),第一個線程執(zhí)行 exchange 方法后會阻塞等待第二個線程執(zhí)行該方法。當兩個線程都到達同步點時這兩個線程就可以交換數(shù)據(jù)當兩個格子中的均被填充時,該對象會自動將兩個格子的信息交換,然后返回給線程,從而實現(xiàn)兩個線程的信息交換。
簡述ConcurrentHashMap
JDK7采用鎖分段技術。首先將數(shù)據(jù)分成 Segment 數(shù)據(jù)段,然后給每一個數(shù)據(jù)段配一把鎖,當一個線程占用鎖訪問其中一個段的數(shù)據(jù)時,其他段的數(shù)據(jù)也能被其他線程訪問。
get 除讀到空值不需要加鎖。該方法先經(jīng)過一次再散列,再用這個散列值通過散列運算定位到 Segment,最后通過散列算法定位到元素。put 須加鎖,首先定位到 Segment,然后進行插入操作,第一步判斷是否需要對 Segment 里的 HashEntry 數(shù)組進行擴容,第二步定位添加元素的位置,然后將其放入數(shù)組。
JDK8的改進
- 取消分段鎖機制,采用CAS算法進行值的設置,如果CAS失敗再使用 synchronized 加鎖添加元素
- 引入紅黑樹結構,當某個槽內(nèi)的元素個數(shù)超過8且 Node數(shù)組 容量大于 64 時,鏈表轉為紅黑樹。
- 使用了更加優(yōu)化的方式統(tǒng)計集合內(nèi)的元素數(shù)量。
Synchronized底層實現(xiàn)原理
Java 對象底層都關聯(lián)一個的 monitor,使用 synchronized 時 JVM 會根據(jù)使用環(huán)境找到對象的 monitor,根據(jù) monitor 的狀態(tài)進行加解鎖的判斷。如果成功加鎖就成為該 monitor 的唯一持有者,monitor 在被釋放前不能再被其他線程獲取。
synchronized在JVM編譯后會產(chǎn)生monitorenter 和 monitorexit 這兩個字節(jié)碼指令,獲取和釋放 monitor。這兩個字節(jié)碼指令都需要一個引用類型的參數(shù)指明要鎖定和解鎖的對象,對于同步普通方法,鎖是當前實例對象;對于靜態(tài)同步方法,鎖是當前類的 Class 對象;對于同步方法塊,鎖是 synchronized 括號里的對象。
執(zhí)行 monitorenter 指令時,首先嘗試獲取對象鎖。如果這個對象沒有被鎖定,或當前線程已經(jīng)持有鎖,就把鎖的計數(shù)器加 1,執(zhí)行 monitorexit 指令時會將鎖計數(shù)器減 1。一旦計數(shù)器為 0 鎖隨即就被釋放。
Synchronized關鍵詞使用方法
- 直接修飾某個實例方法
- 直接修飾某個靜態(tài)方法
- 修飾代碼塊
簡述java偏向鎖
JDK 1.6 中提出了偏向鎖的概念。該鎖提出的原因是,開發(fā)者發(fā)現(xiàn)多數(shù)情況下鎖并不存在競爭,一把鎖往往是由同一個線程獲得的。偏向鎖并不會主動釋放,這樣每次偏向鎖進入的時候都會判斷該資源是否是偏向自己的,如果是偏向自己的則不需要進行額外的操作,直接可以進入同步操作。
其申請流程為:
- 首先需要判斷對象的 Mark Word 是否屬于偏向模式,如果不屬于,那就進入輕量級鎖判斷邏輯。否則繼續(xù)下一步判斷;
- 判斷目前請求鎖的線程 ID 是否和偏向鎖本身記錄的線程 ID 一致。如果一致,繼續(xù)下一步的判斷,如果不一致,跳轉到步驟4;
- 判斷是否需要重偏向。如果不用的話,直接獲得偏向鎖;
- 利用 CAS 算法將對象的 Mark Word 進行更改,使線程 ID 部分換成本線程 ID。如果更換成功,則重偏向完成,獲得偏向鎖。如果失敗,則說明有多線程競爭,升級為輕量級鎖。
簡述輕量級鎖
輕量級鎖是為了在沒有競爭的前提下減少重量級鎖出現(xiàn)并導致的性能消耗。
其申請流程為:
- 如果同步對象沒有被鎖定,虛擬機將在當前線程的棧幀中建立一個鎖記錄空間,存儲鎖對象目前 Mark Word 的拷貝。
- 虛擬機使用 CAS 嘗試把對象的 Mark Word 更新為指向鎖記錄的指針
- 如果更新成功即代表該線程擁有了鎖,鎖標志位將轉變?yōu)?00,表示處于輕量級鎖定狀態(tài)。
- 如果更新失敗就意味著至少存在一條線程與當前線程競爭。虛擬機檢查對象的 Mark Word 是否指向當前線程的棧幀
- 如果指向當前線程的棧幀,說明當前線程已經(jīng)擁有了鎖,直接進入同步塊繼續(xù)執(zhí)行
- 如果不是則說明鎖對象已經(jīng)被其他線程搶占。
- 如果出現(xiàn)兩條以上線程爭用同一個鎖,輕量級鎖就不再有效,將膨脹為重量級鎖,鎖標志狀態(tài)變?yōu)?10,此時Mark Word 存儲的就是指向重量級鎖的指針,后面等待鎖的線程也必須阻塞。
簡述鎖優(yōu)化策略
即自適應自旋、鎖消除、鎖粗化、鎖升級等策略偏。
簡述java的自旋鎖
線程獲取鎖失敗后,可以采用這樣的策略,可以不放棄 CPU ,不停的重試內(nèi)重試,這種操作也稱為自旋鎖。
簡述自適應自旋鎖
自適應自旋鎖自旋次數(shù)不再人為設定,通常由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態(tài)決定。
簡述鎖粗化
鎖粗化的思想就是擴大加鎖范圍,避免反復的加鎖和解鎖。
簡述鎖消除
鎖消除是一種更為徹底的優(yōu)化,在編譯時,java編譯器對運行上下文進行掃描,去除不可能存在共享資源競爭的鎖。
簡述Lock與ReentrantLock
Lock 接是 java并發(fā)包的頂層接口。
可重入鎖 ReentrantLock 是 Lock 最常見的實現(xiàn),與 synchronized 一樣可重入。ReentrantLock 在默認情況下是非公平的,可以通過構造方法指定公平鎖。一旦使用了公平鎖,性能會下降。
簡述AQS
AQS(AbstractQuenedSynchronizer)抽象的隊列式同步器。AQS是將每一條請求共享資源的線程封裝成一個鎖隊列的一個結點(Node),來實現(xiàn)鎖的分配。AQS是用來構建鎖或其他同步組件的基礎框架,它使用一個 volatile int state 變量作為共享資源,如果線程獲取資源失敗,則進入同步隊列等待;如果獲取成功就執(zhí)行臨界區(qū)代碼,釋放資源時會通知同步隊列中的等待線程。
子類通過繼承同步器并實現(xiàn)它的抽象方法getState、setState 和 compareAndSetState對同步狀態(tài)進行更改。
AQS獲取獨占鎖/釋放獨占鎖原理
獲?。?acquire)
- 調用 tryAcquire 方法安全地獲取線程同步狀態(tài),獲取失敗的線程會被構造同步節(jié)點并通過 addWaiter 方法加入到同步隊列的尾部,在隊列中自旋。
- 調用 acquireQueued 方法使得該節(jié)點以死循環(huán)的方式獲取同步狀態(tài),如果獲取不到則阻塞。
釋放:(release)
- 調用 tryRelease 方法釋放同步狀態(tài)
- 調用 unparkSuccessor 方法喚醒頭節(jié)點的后繼節(jié)點,使后繼節(jié)點重新嘗試獲取同步狀態(tài)。
AQS獲取共享鎖/釋放共享鎖原理
獲取鎖(acquireShared)
調用 tryAcquireShared 方法嘗試獲取同步狀態(tài),返回值不小于 0 表示能獲取同步狀態(tài)。
釋放(releaseShared)
釋放,并喚醒后續(xù)處于等待狀態(tài)的節(jié)點。
線程池類型
- newCachedThreadPool 可緩存線程池,可設置最小線程數(shù)和最大線程數(shù),線程空閑1分鐘后自動銷毀。
- newFixedThreadPool 指定工作線程數(shù)量線程池。
- newSingleThreadExecutor 單線程Executor。
- newScheduleThreadPool 支持定時任務的指定工作線程數(shù)量線程池。
- newSingleThreadScheduledExecutor 支持定時任務的單線程Executor。