多線程常問的問題

併發和並行:

併發:是同一時間多個線程同事在做。

並行:同一時刻多個事件同事進行。

 

線程和進程的關係:

進程是由線程組成,每個進程的資源是獨立的,但是該進程中的資源線程是共享的。

線程是cpu調度的基本單位。

 

守護線程是作爲一個提供服務的線程,只當它服務的所有的線程都關閉之後才關閉的線程。

 

創建線程的集中方式:

new Thread();

new Runnable();

線程池創建。

Callable接口和Future接口,future接口是線程執行結果返回的接口。

 

runnable和callable的接口:

runnable 和 callable 都是線程創建的接口。

runnable 接口中必須實現的方法是run方法,返回值void;

callable 接口中中必須實現的是call方法,返回值是一個泛型,接受主體是future和futurtask

 

線程有新建狀態, 停止,等待, 超時等待,運行,阻塞。

 

sleep方法和wait方法都是加了同步鎖的,在sleep方法中,會讓出cpu,但是不會放棄同步鎖,但是wait是放棄同步鎖和cpu。

sleep是睡眠指定時間後繼續執行,wait只阻塞,只有等notify方法纔可以解開阻塞狀態,當然wait可以家超時時間。

wait方法只能在同步塊,同步方法中使用,或者是在加了鎖之後使用。

sleep是Thread的靜態方法,作用與當前線程;wait是object方法,每個對象都可以調用。

 

run方法是runnable接口中的方法,是線程執行的內容;線程調用start方法調用run方法,不可以直接調用run方法,如果直接調用就是屬於方法的正常調用,而不是開啓線程。

 

原子性,可見性,有序性。有序性包含在happens-before原則。

 

線程同步集中方式:

同步鎖。

join,wait等阻塞方法

信號量

特殊變量和volatile關鍵字。

 

Thread.interrupt方法只讓當前線程執行中斷,但是中斷只是一個狀態的改變,並沒有實際的操作,而實際的操作是在其他線程中判斷了中斷狀態之後才進行的根據業務的實際操作。

 

synchronized:

3種形式:

修飾實例方法,對調用對象加鎖。

修飾靜態方法,對類對象加鎖,意思是對該類對象的所有實例都可以使用。原理是對方法加標識,ACC_SUNCHRONIZED

方法塊:雙保險的單例案例,注意對象應該用volatile修飾,原理是在開始和結束時,會有標識,monitorEntry和moniterExit的來標識方法塊的進入和退出。

 

偏向鎖,輕量級鎖,重量級鎖:

偏向鎖是在無競爭條件下的鎖,獲取鎖對象的線程總是哪一個,那麼給鎖加個標識,在線程每次進入的時候都不會重新獲取,而是驗證一下即可。如果再次進入不能獲取到偏向鎖的時候,那麼就會自動將偏向鎖升級爲輕量級鎖。輕量級鎖首先會經歷無所狀態,然後利用cas形式進行自旋,當自旋次數超過限定值,會自動升級爲重量級鎖。

 

自旋的概念:

當線程失去cpu使用權,或者失去了鎖,那麼該線程會進入一個阻塞隊列中,在該阻塞隊列的線程會一直自我詢問,我的前一個節點是否是頭節點,我能否獲取獲取到鎖,拿到cpu控制權,如果可以出隊列,獲取鎖,如果不可以出隊列,加到隊列末尾,繼續自旋。

 

鎖銷除和鎖粗話:

在編譯階段,編譯器檢測到數據不會存在競爭關係,那麼會自動把加在競爭數據上的鎖給去掉。

 

synchronized和reentrantLock:

根本來說synchronized是關鍵字,而reentrantlock是一個類,基於此,後者就有類的特性,所以,方法的多樣,靈活,全面。比如可以支持超時獲取,可以支持中斷等,另外他們的實現原理不同,後者是使用系統哭中的unsafe類的方法實現的(cas也是利用此中方法實現)。他們同樣都支持重入鎖。

 

總結起來,後者比前者多了,都支持可重入,默認是非公平鎖,獲取鎖中斷,獲取鎖超時,可實現公平鎖,支持condition類的監控(其實就是在實現接口的是綁定多個條件),性能好。 所以中斷等待是接口reentrantLock實現的;該接口可以實現公平鎖和非公平鎖,syn只能實現非公平;選擇通知,在syn中一些阻塞喚醒靠object的基礎方法,而lock中可以得到監控器condition,每一個鎖可以創建多個condition,而condition也具有靈活性,支持很多方法;在幸能方面,倆者在1.6後持平。

 

syn和volatile的區別:

最主要syn支持原子性,volatile不支持; 前者修飾方法,方法快,後者之修飾變量;前者造成方法阻塞,後者操作內存更新數據;前者可以被重排序優化,後者不會發生重排序優化。

 

重排序: 爲了讓減少阻塞,所以在編譯階段和cpu執行階段都會執行重排序。

 

volatile保證了可見性,不保證原子性;而可見性是java內存模型保障的。用volatile修飾之後,該變量會在內存中保留一份,而每個線程都會保存一份他的副本,都某一個副本的值被改變之後,該值會刷到主內存中,並聲明其他副本的值是無效,所以其他副本想要使用值得時候,只能重新從主內存中獲取。

 

重入鎖和讀寫鎖得區別(ReentrantLock 和 ReentrantReadWriteLock):

重入鎖是獨佔鎖,同時只允許一個線程操作;

讀寫鎖允許多個線程同時操作,但不允許讀寫,寫寫同時操作。

他們都是基於AQS實現;

讀寫鎖得同步器時基於AQS實現的,需要在同步狀態上維護多個讀線程和一個寫線程,

讀寫鎖實現原理時利用高低位,在32位得鎖標識中,高16位代表讀鎖,低16位代表寫;

讀鎖獲取過程:獲取鎖,鎖狀態是否位0,不是0,查看低16是否爲0,xx

 

讀寫鎖特點:

寫鎖可以降級爲讀鎖,讀不可以降級爲寫鎖;

都支持獲取鎖中斷;

寫鎖可以拿到監聽器condition;讀鎖不可以;

默認時非公平,因爲非公平效率更高;

在讀鎖中不能加寫鎖,會死鎖;

寫鎖中可以加讀鎖,時所降級;

 

悲觀鎖:

數據庫中,表鎖,行鎖,讀鎖,寫鎖;Java中synchronized和reentrantLock,適用於寫多讀少得場景;

樂觀鎖:採用cas算法得設計,比如數據庫得樂觀鎖,Java中原子類,使用讀多寫少場景;

cas: 需要知道更新內存中那個值,需要知道原來得值, 需要知道改變後的值;

cas 得aba問題: 一個線程將數值從 a改成b,另一個線程由b改成a,從結果上看是沒有問題得,但是並不能知道是否做了改變;

cas得循環時間開銷大得問題: 自旋式cas代表如果不能成功更新就會一直循環,對cpu得損耗很大。

cas得只能保證一個共享變量得原子操作,當操作一個共享變量得時候有效,當操作多個共享變狼得時候是無效得。所以可以通過另一個原子操作類,atomicReference來操作對象得cas更新;

 

 

AQS(abstractQueuedSynchronizer):

aqs是構建鎖和同步器的框架。

aqs原理:

核心思想:

如果請求的資源空閒,則將當前請求的資源線程設置爲有效的資源線程,並將資源設置爲鎖定狀態,如果資源處於鎖定狀態,則需要一套阻塞等待和喚醒所分配的機制,這個機制使用CLH隊列實現的,並將暫時獲取不到鎖的線程加入到隊列中。

CLH隊列是一個虛擬的雙向隊列(虛擬是說不純在的意思,只有前後節點關聯關係的信息而已)

aqs使用一個int成員變量state來標識同步狀態,通過內置FIFO 獲取線程排隊工作,AQS使用CAS對該狀態的修改進行跟蹤;通過getState, setState 和compareSetState進行操作。

共享方式:

獨佔 (exclusive) 和共享 (share)

獨佔鎖分爲公平和非公平, 公平是按照CLH隊列順序那鎖,不公平是無視隊列順序搶鎖。

不同的自定義同步器徵用共享資源的方式也是不一樣的,自定同步器在實現時是需要實現資源state的獲取和釋放即可,至於具體線程隊列的維護,aqs在上層已經幫我們實現了。

底層是模板方法模式,所以重寫aqs的方法時,需要將其中的幾個方法重寫了。

應用:countdownlatch; Semaphore; cycliBarrier; 重入鎖和讀寫鎖;

Semaphore 時共享的,同時最多同時又n個線程同時執行,可以分爲公平和非公平狀態。

countdownlatch 讓執行完邏輯的n個線程進行等待,當n個線程執行完成的時候再繼續執行。

countdownlatch三種場景:

當一個線程運行時,需要等待n個線程運行完畢;

實現多個線程開始執行任務的最大並行量,發令槍式的場景。

死鎖檢測:

cyclicBarrier: 循環屏障; 當一組線程都到達屏障的時候,程序纔可以繼續執行。每個線程到達屏障的時候執行await表示到達屏障,然後線程被阻塞。

場景是在銀行計算流水合計。

 

countdownlatch和cycliBarrier的區別:

cylicBarrier可以使用reset功能,多次使用,而countdownlatch只能使用一次。

 

線程池:

優點:更好管理,降低資源消耗,提高相應。

Runnable和Callable接口:

Executors.callable(Runnable task);

Executors.callable(Runnable task, Object result);

 

execute()和submit()區別:

execute()不需要返回結果,submit可以返回結果,返回的是futur接口,並有一個泛型標識返回結果類型,然後利用get方法阻塞獲取結果,也可以設置超時時間。

 

線程池設置參數:

核心線程個數:調用prestartAllCoreThreads()方法,提前啓動核心線程;

最大線程數;

保活單位:

保活時間:

阻塞隊列: ArrayBlockingQueue; LinkedBlockingQueue;SynchronousQueue; PriorityBlcokingQueue;

arr,必須設置隊列大小,且需要設置公平,或者是非公平;

飽和策略:

拒絕; 直接拋出異常; 隨機丟棄隊列中的線程; 用調用者線程來執行任務;

 

線程數選擇:

1. io密集型,因爲壓力在io上,而不在cpu上,所以建議選擇數量大的線程;

2.cpu密集,cpu壓力大,應該根據實際情況確定,一般是計算機邏輯內核數;

3.內存使用:

4.下游的抗併發能力:比如直接查詢數據庫,調用下游接口時都需要考量;

 

併發容器:

ConcurrentHashMap;線程安全的HashMap

CopyOnWriteArrayList:線程安全的list, 在讀多寫少的場景下,遠好於Vector

ConcurrentLinkedQueue: 高效併發隊列,使用鏈表實現,可以看作時線程安全的LinkedList,時一個非阻塞隊列,利用cas實現的。

BlockingQueue: 這個一個接口,JDK內部通過數組和鏈表實現了它,標識阻塞隊列,非常適合用於數據共性的通道; 當隊列是空的時候,取數據的方法阻塞;當隊列慢的時候,放數據的方法阻塞;

ConcurrentSkipListMap:跳錶的實現,可快速查找。

ConcurrentSkipListMap: 對平衡樹的增刪會導致對整個平衡樹的調整,但是對調錶的節點插入修改只需要對全局做修改即可。

實戰:

在AQS中,大量使用了LockSupport類,尤其是其中的park和unpark方法,這個方法的作用和wait和notify方法類似,但是locksupport可以不先獲取鎖,所以當執行unpark方法的時候,是不會報出中斷異常的,但會講鎖的狀態設設置改變,也就是後來,正式對他做park處理的時候,是不能成功的。

如果利用wait和notify方法,最後注意是否有方法一直在阻塞,是否需要使用ontify喚醒最後阻塞的線程。

lock.newCondition創建出的一個東西其實是一個隊列,那麼我們對對這個lock去創建n個condition,就是n個隊列,那麼這樣的好處就是我們可以對每個隊列中的線程進行控制,比如說await,或者是signal。

 

TransferQueue:容量是0,是因爲是生產者阻塞模式,就是說生產者放入道隊列中消息之後,就開始阻塞,知道消費者取出消息來,纔可以繼續執行。 這個和現實中的場景類似的是,遞東西,只有有人接才性,如果講遞的過程放到一個容器中,那麼transferqueue就是這個容器。

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