1.進程和線程還有協程之間的關系
1.1 進程,直觀點說,保存在硬盤上的程序運行以后,會在內存空間里形成一個獨立的內存體,這個內存體有自己獨立的地址空間,有自己的堆,上級掛靠單位是操作系統。
操作系統會以進程為單位,分配系統資源(CPU時間片、內存等資源),進程是資源分配的最小單位。
1.3 協程,協程是一種用戶態的輕量級線程,協程的調度完全由用戶控制。協程擁有自己的寄存器上下文和棧。
協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,直接操作棧則基本沒有內核切換的開銷,可以不加鎖的訪問全局變量,所以上下文的切換非常快。
協程在子程序內部可中斷的,然后轉而執行別的子程序,在適當的時候再返回來接着執行。
參考:https://www.cnblogs.com/starluke/p/11795342.html
2.並發和並行之間的區別
並發:指統一時間內,宏觀上處理多個任務
並行:指統一時間內,真正上處理多個任務
3.Java中多線程實現的方式
3.1 繼承Thread類,重寫run方法
3.2 實現Runnable接口,重寫run方法
3.3 通過Callable和FutureTask創建線程
3.4 通過線程池創建線程
4.Callable和Future模式
Callable
在Java中,創建線程一般有兩種方式,一種是繼承Thread類,一種是實現Runnable接口。然而,這兩種方式的缺點是在線程任務執行結束后,無法獲取執行結果。我們一般只能采用共享變量或共享存儲區以及線程通信的方式實現獲得任務結果的目的。
不過,Java中,也提供了使用Callable和Future來實現獲取任務結果的操作。Callable用來執行任務,產生結果,而Future用來獲得結果。
Callable接口的定義如下:
public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }
與Runnable接口不同之處在於,call方法帶有泛型返回值V。
Future模式
Future模式的核心在於:去除了主函數的等待時間,並使得原本需要等待的時間段可以用於處理其他業務邏輯
Futrure模式:對於多線程,如果線程A要等待線程B的結果,那么線程A沒必要等待B,直到B有結果,可以先拿到一個未來的Future,等B有結果是再取真實的結果。
在多線程中經常舉的一個例子就是:網絡圖片的下載,剛開始是通過模糊的圖片來代替最后的圖片,等下載圖片的線程下載完圖片后在替換。而在這個過程中可以做一些其他的事情。
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** * @author wn: * @version 上午16:53:57 * 類說明 */ public class ThreadCallTest { public static void main(String[]args){ ExecutorService executor=Executors.newCachedThreadPool(); Task task=new Task(); Future<Integer> result=executor.submit(task); if (executor != null) executor.shutdown(); try { System.out.println("call result"+result.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("over"); } } class Task implements Callable<Integer>{ @Override public Integer call() throws Exception { System.out.println("3.開始 ...."); Thread.sleep(3000); System.out.println("4.結束 ...."); return "xyz"; } }
當Task啟動后不影響主線程運行,result.get()會等待3秒后返回結果xyz
Future常用方法
V get() :獲取異步執行的結果,如果沒有結果可用,此方法會阻塞直到異步計算完成。
V get(Long timeout , TimeUnit unit) :獲取異步執行結果,如果沒有結果可用,此方法會阻塞,但是會有時間限制,如果阻塞時間超過設定的timeout時間,該方法將拋出異常。
boolean isDone() :如果任務執行結束,無論是正常結束或是中途取消還是發生異常,都返回true。 => result.isDone()
boolean isCanceller() :如果任務完成前被取消,則返回true。
boolean cancel(boolean mayInterruptRunning) :如果任務還沒開始,執行cancel(...)方法將返回false;如果任務已經啟動,執行cancel(true)方法將以中斷執行此任務線程的方式來試圖停止任務,如果停止成功,返回true;
當任務已經啟動,執行cancel(false)方法將不會對正在執行的任務線程產生影響(讓線程正常執行到完成),此時返回false;
當任務已經完成,執行cancel(...)方法將返回false。mayInterruptRunning參數表示是否中斷執行中的線程。
實際上Future提供了3種功能:
- (1)能夠中斷執行中的任務
- (2)判斷任務是否執行完成
- (3)獲取任務執行完成后的結果
5.線程池創建的方式(一般不適用Excecutors.newxxxx創建,一般使用ThreadPoolExecutor)
newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
newFixedThreadPool 創建一個定長線程池,可控制線程最大並發數,超出的線程會在隊列中等待。
newScheduledThreadPool 創建一個定長線程池,支持定時及周期性任務執行。
newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
6.Java當中線程狀態有哪些
6.1 新建狀態(New): 線程對象被創建后,就進入了新建狀態。例如,Thread thread = new Thread()。
6.2 就緒狀態(Runnable): 也被稱為“可執行狀態”。線程對象被創建后,其它線程調用了該對象的start()方法,從而來啟動該線程。例如,thread.start()。處於就緒狀態的線程,隨時可能被CPU調度執行。
6.3 運行狀態(Running): 線程獲取CPU權限進行執行。需要注意的是,線程只能從就緒狀態進入到運行狀態。
6.4 阻塞狀態(Blocked): 阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種:
- (01) 等待阻塞 -- 通過調用線程的wait()方法,讓線程等待某工作的完成。
- (02) 同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因為鎖被其它線程所占用),它會進入同步阻塞狀態。
- (03) 其他阻塞 -- 通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
6.5 死亡狀態(Dead): 線程執行完了或者因異常退出了run()方法,該線程結束生命周期。
7.多線程中的常用方法
Java多線程中的常用方法有如下幾個
start,run,sleep,wait,notify,notifyAll,join,isAlive,currentThread,interrupt
1)start方法
用於啟動一個線程,使相應的線程進入排隊等待狀態。一旦輪到它使用CPU的資源的時候,它就可以脫離它的主線程而獨立開始
自己的生命周期了。注意即使相應的線程調用了start方法,但相關的線程也不一定會立刻執行,調用start方法的主要目的是使
當前線程進入排隊等待。不一定就立刻得到cpu的使用權限...
2)run方法
Thread類和Runnable接口中的run方法的作用相同,都是系統自動調用而用戶不得調用的。
3)sleep和wait方法
Sleep:是Java中Thread類中的方法,會使當前線程暫停執行讓出cpu的使用權限。但是監控狀態依然存在,即如果當前線程
進入了同步鎖的話,sleep方法並不會釋放鎖,即使當前線程讓出了cpu的使用權限,但其它被同步鎖擋在外面的線程也無法獲
得執行。待到sleep方法中指定的時間后,sleep方法將會繼續獲得cpu的使用權限而后繼續執行之前sleep的線程。
Wait:是Object類的方法,wait方法指的是一個已經進入同步鎖的線程內,讓自己暫時讓出同步鎖,以便其它正在等待此同步
鎖的線程能夠獲得機會執行。,只有其它方法調用了notify或者notifyAll(需要注意的是調用notify或者notifyAll方法並不釋放
鎖,只是告訴調用wait方法的其它 線程可以參與鎖的競爭了..)方法后,才能夠喚醒相關的線程。此外注意wait方法必須在同步關
鍵字修飾的方法中才能調用。
4) notify和notifyAll
釋放因為調用wait方法而正在等待中的線程。notify和notifyAll的唯一區別在於notify喚醒某個正在等待的線程。而notifyAll會喚醒
所有正在等等待的線程。需要注意的是notify和notifyAll並不會釋放對應的同步鎖哦。
5) isAlive
檢查線程是否處於執行狀態。在當前線程執行完run方法之前調用此方法會返回true。
在run方法執行完進入死亡狀態后調用此方法會返回false。
6) currentThread
Thread類中的方法,返回當前正在使用cpu的那個線程。
7) intertupt
吵醒因為調用sleep方法而進入休眠狀態的方法,同時會拋出InterrruptedException哦。
8)join
線程聯合 例如一個線程A在占用cpu的期間,可以讓其它線程調用join()和本地線程聯合。
9)yield()
調用yield方法會讓當前線程交出CPU權限,讓CPU去執行其他的線程。它跟sleep方法類似,同樣不會釋放鎖。但是yield不能控制具體的交出CPU的時間,另外,yield方法只能讓擁有相同優先級的線程有獲 取CPU執行時間的機會。
注意,調用yield方法並不會讓線程進入阻塞狀態,而是讓線程重回就緒狀態,它只需要等待重新獲取CPU執行時間,這一點是和sleep方法不一樣的。
8.線程狀態流程圖
9.volatile關鍵字有什么用途,和Synchronize有什么區別
volatile關鍵字的作用
其實volatile關鍵字的作用就是保證了可見性和有序性(不保證原子性),如果一個共享變量被volatile關鍵字修飾,那么如果一個線程修改了這個共享變量后,其他線程是立馬可知的。為什么是這樣的呢?比如,線程A修改了自己的共享變量副本,這時如果該共享變量沒有被volatile修飾,那么本次修改不一定會馬上將修改結果刷新到主存中,如果此時B去主存中讀取共享變量的值,那么這個值就是沒有被A修改之前的值。如果該共享變量被volatile修飾了,那么本次修改結果會強制立刻刷新到主存中,如果此時B去主存中讀取共享變量的值,那么這個值就是被A修改之后的值了。
volatile能禁止指令重新排序,在指令重排序優化時,在volatile變量之前的指令不能在volatile之后執行,在volatile之后的指令也不能在volatile之前執行,所以它保證了有序性。
synchronized關鍵字的作用
synchronized提供了同步鎖的概念,被synchronized修飾的代碼段可以防止被多個線程同時執行,必須一個線程把synchronized修飾的代碼段都執行完畢了,其他的線程才能開始執行這段代碼。
因為synchronized保證了在同一時刻,只能有一個線程執行同步代碼塊,所以執行同步代碼塊的時候相當於是單線程操作了,那么線程的可見性、原子性、有序性(線程之間的執行順序)它都能保證了。
volatile關鍵字和synchronized關鍵字的區別
(1)、volatile只能作用於變量,使用范圍較小。synchronized可以用在變量、方法、類、同步代碼塊等,使用范圍比較廣。
(2)、volatile只能保證可見性和有序性,不能保證原子性。而可見性、有序性、原子性synchronized都可以保證。
(3)、volatile不會造成線程阻塞。synchronized可能會造成線程阻塞。
10.指令重排和先行發生原則
Java指令重排序
Java先行發生原則(Happen-Before)
11.並發編程線程安全三要素
保證線程安全的三個方面
12.進程和線程間調度算法:
12.1先來先服務(FCFS)
先來先服務(first-come first-served,FCFS):是最簡單的調度算法,按照請求的先后順序進行調度。
特點:
非搶占式的調度算法,易於實現,但性能不好
有利於長作業,不利於短作業。 因為短作業必須等待前面的長作業執行完畢才能執行,會造成短作業的等待時間過長。
12.2短作業優先(SJF)
短作業優先(shortest job first,SJF):按估計運行時間最短的順序進行調度。
特點:
非搶占式的調度算法,優先照顧短作業,具有很好的性能,降低平均等待時間,提高吞吐量。
不利於長作業,長作業可能一直處於等待狀態,造成長作業餓死。
沒有考慮作業的優先緊迫程度,不能用於實時系統。
12.3最短剩余時間優先(SRTN)
最短剩余時間優先 (shortest remaining time next, SRTN):
最短剩余時間優先是短作業優先的搶占式版本,按剩余運行時間最短的剩余進行調度。
新的進程到來時,將新進程的運行時間與當前進程的剩余運行時間進行比較。
如果新進程的運行時間更短,則掛起當前進程,運行新進程;否則新進程等待。
特點: 確保一旦新的短進程進入系統,可以盡快處理。
12.4時間片輪轉
時間片輪轉(round robin,RR):
使用隊列,按照FCFS的原則將就緒進程排序,后來的進程插入到隊列末尾。
選擇隊列中的首進程,為其分配CPU時間片。時間片用完,計時器發出時鍾中斷,停止該進程的運行,將其送回隊列末尾。
重復步驟1和2,直到完成進程調度。
特點:
時間片輪轉的效率與時間片的大小有很大關系。 時間片太小,進程切換頻繁反而使CPU利用率變低;時間片太大,系統的實時性不能得到保證。
時間片輪轉算法,大多用於分時系統。
12.5優先級調度
優先級調度:為每個進程分配一個優先級,優先級高的先調度,優先級低后調度。
根據當前進程執行時,遇到較高優先級進程的處理方式,分為搶占式優先級調度、非搶占式優先級調度。
搶占式優先級調度:進程在執行期間,具有更高優先級的進程到來,則中斷當前進程,執行具有更高優先級的進程。
非搶占式優先級調度:進程在執行期間,具有更高優先級的進程到來,仍然繼續執行直到完成。
為了防止低優先級的進程永遠等不到調度,可以隨着時間的推移增加等待進程的優先級。
12.6多級反饋隊列
多級反饋隊列:
設置多個隊列,每個隊列的時間片大小不同,例如1, 2, 4, 8 ...。
進程就緒時首先進入一級隊列,被調度執行后,如果還需執行,則進入二級隊列,依次類推。如果到了最后一級隊列還未執行完畢,則仍然進入該隊列。
每一級隊列中的進程按照FCFS進行調度,只有當上一級隊列中沒有進程等待執行時,才能調度當前隊列中的進程。
多級反饋隊列是為連續執行多個時間片的進程考慮的,通過更改每級隊列的時間片大小,減少該進程的調度次數。
參考:操作系統之進程和線程(進程調度算法、進程間通信)
Java中采用搶占式
13.Java開發中用過哪些鎖:
13.1 樂觀鎖
樂觀鎖顧名思義,就是很樂觀,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS(Compare and Swap 比較並交換)實現的
樂觀鎖適合讀操作非常多的場景,不加鎖會帶來大量的性能提升;
樂觀鎖在Java中的使用,是無鎖編程,常常采用的是CAS算法,典型的例子就是原子類,通過CAS自旋實現原子操作的更新。
13.2 悲觀鎖
悲觀鎖總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖。比如Java里面的同步原語synchronized關鍵字的實現就是悲觀鎖。
悲觀鎖適合寫操作非常多的場景;
悲觀鎖在Java中的使用,就是利用各種鎖;
13.3 獨享鎖
獨享鎖是指該鎖一次只能被一個線程所持有。
獨享鎖通過AQS來實現的,通過實現不同的方法,來實現獨享鎖。
對於Synchronized而言,當然是獨享鎖。
13.4 共享鎖
共享鎖是指該鎖可被多個線程所持有。
讀鎖的共享鎖可保證並發讀是非常高效的,讀寫,寫讀,寫寫的過程是互斥的。
共享鎖也是通過AQS來實現的,通過實現不同的方法,來實現共享鎖。
13.5 互斥鎖
互斥鎖在Java中的具體實現就是ReentrantLock。
13.6 讀寫鎖
讀寫鎖在Java中的具體實現就是ReadWriteLock。
13.7 可重入鎖
重入鎖也叫作遞歸鎖,指的是同一個線程外層函數獲取到一把鎖后,內層函數同樣具有這把鎖的控制權限;
synchronized和ReentrantLock就是重入鎖對應的實現;
synchronized重量級的鎖 ;
ReentrantLock輕量級的鎖;
13.8 公平鎖
公平鎖是指多個線程按照申請鎖的順序來獲取鎖。
對於Java ReetrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在於吞吐量比公平鎖大。
13.9 非公平鎖
非公平鎖是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能后申請的線程比先申請的線程優先獲取鎖。有可能,會造成優先級反轉或者飢餓現象。
對於Synchronized而言,也是一種非公平鎖。由於其並不像ReentrantLock是通過AQS的來實現線程調度,所以並沒有任何辦法使其變成公平鎖。
13.10 分段鎖
分段鎖其實是一種鎖的設計,並不是具體的一種鎖,對於ConcurrentHashMap而言,其並發的實現就是通過分段鎖的形式來實現高效的並發操作。
我們以ConcurrentHashMap來說一下分段鎖的含義以及設計思想,ConcurrentHashMap中的分段鎖稱為Segment,它即類似於HashMap(JDK7和JDK8中HashMap的實現)的結構,即內部擁有一個Entry數組,數組中的每個元素又是一個鏈表;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。
當需要put元素的時候,並不是對整個hashmap進行加鎖,而是先通過hashcode來知道他要放在哪一個分段中,然后對這個分段進行加鎖,所以當多線程put的時候,只要不是放在一個分段中,就實現了真正的並行的插入。
但是,在統計size的時候,可就是獲取hashmap全局信息的時候,就需要獲取所有的分段鎖才能統計。
分段鎖的設計目的是細化鎖的粒度,當操作不需要更新整個數組的時候,就僅僅針對數組中的一項進行加鎖操作。
13.11 偏向鎖
偏向鎖是指一段同步代碼一直被一個線程所訪問,那么該線程會自動獲取鎖。降低獲取鎖的代價。
13.12 輕量級鎖
輕量級鎖是指當鎖是偏向鎖的時候,被另一個線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,提高性能。
13.13 重量級鎖
重量級鎖是指當鎖為輕量級鎖的時候,另一個線程雖然是自旋,但自旋不會一直持續下去,當自旋一定次數的時候,還沒有獲取到鎖,就會進入阻塞,該鎖膨脹為重量級鎖。重量級鎖會讓他申請的線程進入阻塞,性能降低。
13.14 自旋鎖
在Java中,自旋鎖是指嘗試獲取鎖的線程不會立即阻塞,而是采用循環的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點是循環會消耗CPU。
14.synchronized關鍵字理解
代表這個方法加鎖,相當於不管哪一個線程(例如線程A),運行到這個方法時,都要檢查有沒有其它線程B(或者C、 D等)正在用這個方法(或者該類的其他同步方法),有的話要等正在使用synchronized方法的線程B(或者C 、D)運行完這個方法后再運行此線程A,沒有的話,鎖定調用者,然后直接運行。它包括兩種用法:synchronized 方法和 synchronized 塊。
在Java中,synchronized關鍵字是用來控制線程同步的,就是在多線程的環境下,控制synchronized代碼段不被多個線程同時執行。
synchronized是Java中的關鍵字,是一種同步鎖。它修飾的對象有以下幾種:
1. 修飾一個代碼塊,被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象;
2. 修飾一個方法,被修飾的方法稱為同步方法,其作用的范圍是整個方法,作用的對象是調用這個方法的對象;
3. 修改一個靜態的方法,其作用的范圍是整個靜態方法,作用的對象是這個類的所有對象;
4. 修改一個類,其作用的范圍是synchronized后面括號括起來的部分,作用主的對象是這個類的所有對象。
15.CAS無鎖機制
CAS無鎖機制
16.AQS
AQS簡介
17.ReentrantLock底層實現
ReentrantLock主要利用CAS+CLH隊列來實現。它支持公平鎖和非公平鎖,兩者的實現類似。
- CAS:Compare and Swap,比較並交換。CAS有3個操作數:內存值V、預期值A、要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做。該操作是一個原子操作,被廣泛的應用在Java的底層實現中。在Java中,CAS主要是由sun.misc.Unsafe這個類通過JNI調用CPU底層指令實現。
- CLH隊列帶頭結點的雙向非循環鏈表
ReentrantLock的基本實現可以概括為:先通過CAS嘗試獲取鎖。如果此時已經有線程占據了鎖,那就加入CLH隊列並且被掛起。當鎖被釋放之后,排在CLH隊列隊首的線程會被喚醒,然后CAS再次嘗試獲取鎖。在這個時候,如果:
- 非公平鎖:如果同時還有另一個線程進來嘗試獲取,那么有可能會讓這個線程搶先獲取;
- 公平鎖:如果同時還有另一個線程進來嘗試獲取,當它發現自己不是在隊首的話,就會排到隊尾,由隊首的線程獲取到鎖
18.ReentrantLock和Synchronized之間的區別
相似點:
兩個都是可重入鎖,它們都是加鎖方式同步,而且都是阻塞式的同步,也就是說當如果一個線程獲得了對象鎖,進入了同步塊,其他訪問該同步塊的線程都必須阻塞在同步塊外面等待,而進行線程阻塞和喚醒的代價是比較高的(操作系統需要在用戶態與內核態之間來回切換,代價很高,不過可以通過對鎖優化進行改善)。
功能區別:
相同點:
1.它們都是加鎖方式同步;
2.都是重入鎖;
3. 阻塞式的同步;也就是說當如果一個線程獲得了對象鎖,進入了同步塊,其他訪問該同步塊的線程都必須阻塞在同步塊外面等待,而進行線程阻塞和喚醒的代價是比較高的(操作系統需要在用戶態與內核態之間來回切換,代價很高,不過可以通過對鎖優化進行改善);
不同點:
參考:https://blog.csdn.net/qq_40551367/article/details/89414446
19.ReentrantReadWriteLock
讀寫鎖簡介
20.BlockingQueue和ConcurrentLinkedQueue簡介
BlockingQueue
阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列,這兩個附加的操作是:
在隊列為空時,獲取元素的線程會等待隊列變為非空;
當隊列滿時,存儲元素的線程會等待隊列可用;
阻塞隊列常用於生產者和消費者的場景,生產者是往隊列里添加元素的線程,消費者是從隊列里拿元素的線程。阻塞隊列就是生產者存放元素的容器,而消費者也只從容器拿元素;
在java中,BlockingQueue的接口位於java.util.concurrent包中,阻塞隊列是線程安全的;
在新增的concurrent包中,BlockingQueue很好的解決了多線程中,如何高效安全“傳輸”數據的問題,通過這些高效並且線程安全的隊列類,為我們快速搭建高質量的多線程程序帶來極大的便利;
常用的隊列主要由以下兩種:
1.先進先出(FIFO):先插入的隊列的元素也最先出隊列,類似於排隊的功能,從某種程度上來說這種隊列也體現了一種公平性;
2.后進后出(LIFO):后插入隊列的元素最先出隊列,這種隊列優先處理最近發生的事件;
ConcurrentLinkedQueue
ConcurrentLinkedQueue : 是一個適用於高並發場景下的隊列,通過無鎖的方式,實現了高並發狀態下的高性能,通常ConcurrentLinkedQueue性能好於BlockingQueue.
它是一個基於鏈接節點的無界線程安全隊列。該隊列的元素遵循先進先出的原則。頭是最先加入的,尾是最近加入的,該隊列不允許null元素。
ConcurrentLinkedQueue重要方法:
add 和offer() 都是加入元素的方法(在ConcurrentLinkedQueue中這倆個方法沒有任何區別)
poll() 和peek() 都是取頭元素節點,區別在於前者會刪除元素,后者不會。
peek和poll當隊列當中沒有數據時,獲取的數據為null,不會產生阻塞