1、并發編程三要素?
1)原子性
原子性指的是一個或者多個操作,要么全部執行并且在執行的程序中不被其他操作打斷,要么就全部都不執行,
2)可見性
可見性指多個執行緒操作一個共享變數時,其中一個執行緒對變數進行修改后,其他執行緒可以立即看到修改的結果,
實作可見性的方法:
synchronized或者Lock:保證同一個時刻只有一個執行緒獲取鎖執行代碼,鎖釋放之前把最新的值重繪到主記憶體,實作可見性,
3)有序性
有序性,即程式的執行順序按照代碼的先后順序來執行,
2、多執行緒的價值?
1)發揮多核CPU的優勢
多執行緒,可以真正發揮出多核CPU的優勢來,達到充分利用CPU的目的,采用多執行緒的方式去同時完成幾件事情而不互相干擾,
2)防止阻塞
從程式運行效率的角度來看,單核CPU不但不會發揮出多執行緒的優勢,反而會因為在單核CPU上運行多執行緒導致執行緒背景關系的切換,而降低程式整體的效率,但是單核CPU我們還是要應用多執行緒,就是為了防止阻塞,試想,如果單核CPU使用單執行緒,那么只要這個執行緒阻塞了,比方說遠程讀取某個資料吧,對端遲遲未回傳又沒有設定超時時間,那么你的整個程式在資料回傳回來之前就停止運行了,多執行緒可以防止這個問題,多條執行緒同時運行,哪怕一條執行緒的代碼執行讀取資料阻塞,也不會影響其它任務的執行,
3)便于建模
這是另外一個沒有這么明顯的優點了,假設有一個大的任務A,單執行緒編程,那么就要考慮很多,建立整個程式模型比較麻煩,但是如果把這個大的任務A分解成幾個小任務,任務B、任務C、任務D,分別建立程式模型,并通過多執行緒分別運行這幾個任務,那就簡單很多了,
3、創建執行緒的有哪些方式?
1)繼承Thread類創建執行緒類
2)通過Runnable介面創建執行緒類
3)通過Callable和Future創建執行緒
4.創建執行緒的三種方式的對比?
1)采用實作Runnable、Callable介面的方式創建多執行緒,
優勢是:
執行緒類只是實作了Runnable介面或Callable介面,還可以繼承其他類,
在這種方式下,多個執行緒可以共享同一個target物件,所以非常適合多個相同執行緒來處理同一份資源的情況,從而可以將CPU、代碼和資料分開,形成清晰的模型,較好地體現了面向物件的思想,
劣勢是:
編程稍微復雜,如果要訪問當前執行緒,則必須使用Thread.currentThread()方法,
2)使用繼承Thread類的方式創建多執行緒
優勢是:
撰寫簡單,如果需要訪問當前執行緒,則無需使用Thread.currentThread()方法,直接使用this即可獲得當前執行緒,
劣勢是:
執行緒類已經繼承了Thread類,所以不能再繼承其他父類,
3)Runnable和Callable的區別
- Callable規定(重寫)的方法是call(),Runnable規定(重寫)的方法是run(),
- Callable的任務執行后可回傳值,而Runnable的任務是不能回傳值的,
- Call方法可以拋出例外,run方法不可以,
- 運行Callable任務可以拿到一個Future物件,表示異步計算的結果,它提供了檢查計算是否完成的方法,以等待計算的完成,并檢索計算的結果,通過Future物件可以了解任務執行情況,可取消任務的執行,還可獲取執行結果,
5、執行緒的狀態流轉圖
執行緒的生命周期及五種基本狀態:
Java執行緒具有五中基本狀態
1)新建狀態(New):當執行緒物件對創建后,即進入了新建狀態,如:Thread t = new MyThread();
2)就緒狀態(Runnable):當呼叫執行緒物件的start()方法(t.start();),執行緒即進入就緒狀態,處于就緒狀態的執行緒,只是說明此執行緒已經做好了準備,隨時等待CPU調度執行,并不是說執行了t.start()此執行緒立即就會執行;
3)運行狀態(Running):當CPU開始調度處于就緒狀態的執行緒時,此時執行緒才得以真正執行,即進入到運行狀態,注:就 緒狀態是進入到運行狀態的唯一入口,也就是說,執行緒要想進入運行狀態執行,首先必須處于就緒狀態中;
4)阻塞狀態(Blocked):處于運行狀態中的執行緒由于某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU呼叫以進入到運行狀態,根據阻塞產生的原因不同,阻塞狀態又可以分為三種:
1.等待阻塞:運行狀態中的執行緒執行wait()方法,使本執行緒進入到等待阻塞狀態;
2.同步阻塞 — 執行緒在獲取synchronized同步鎖失敗(因為鎖被其它執行緒所占用),它會進入同步阻塞狀態;
3.其他阻塞 — 通過呼叫執行緒的sleep()或join()或發出了I/O請求時,執行緒會進入到阻塞狀態,當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態,
5)死亡狀態(Dead):執行緒執行完了或者因例外退出了run()方法,該執行緒結束生命周期,
6.什么是執行緒池? 有哪幾種創建方式?
執行緒池就是提前創建若干個執行緒,如果有任務需要處理,執行緒池里的執行緒就會處理任務,處理完之后執行緒并不會被銷毀,而是等待下一個任務,由于創建和銷毀執行緒都是消耗系統資源的,所以當你想要頻繁的創建和銷毀執行緒的時候就可以考慮使用執行緒池來提升系統的性能,
java 提供了一個 java.util.concurrent.Executor介面的實作用于創建執行緒池,
四種執行緒池的創建:
(1)newCachedThreadPool創建一個可快取執行緒池
(2)newFixedThreadPool 創建一個定長執行緒池,可控制執行緒最大并發數,
(3)newScheduledThreadPool 創建一個定長執行緒池,支持定時及周期性任務執行,
(4)newSingleThreadExecutor 創建一個單執行緒化的執行緒池,它只會用唯一的作業執行緒來執行任務,
7.執行緒池的優點?
1)重用存在的執行緒,減少物件創建銷毀的開銷,
2)可有效的控制最大并發執行緒數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞,
3)提供定時執行、定期執行、單執行緒、并發數控制等功能,
8.Java中的同步集合與并發集合有什么區別?
同步集合類:
- Vector
- Stack
- HashTable
- Collections.synchronized方法生成
并發集合類:
- ConcurrentHashMap
- CopyOnWriteArrayList
- CopyOnWriteArraySet等
9.同步集合與并發集合的區別
同步集合與并發集合都為多執行緒和并發提供了合適的執行緒安全的集合,不過并發集合的可擴展性更高,同步集合比并發集合會慢得多,主要原因是鎖,同步集合會對整個May或List加鎖,而并發集合例如ConcurrentHashMap,
把整個Map 劃分成幾個片段,只對相關的幾個片段上鎖,同時允許多執行緒訪問其他未上鎖的片段(JDK1.8版本底層加入了紅黑樹),
10.常用的并發工具類有哪些?
- CountDownLatch
- CyclicBarrier
- Semaphore
- Exchanger
11.CyclicBarrier和CountDownLatch的應用場景?
CountDownLatch : 一個執行緒(或者多個), 等待另外N個執行緒完成某個事情之后才能執行, CyclicBarrier : N個執行緒相互等待,任何一個執行緒完成之前,所有的執行緒都必須等待,
CountDownLatch的使用場景:
在一些應用場合中,需要等待某個條件達到要求后才能做后面的事情;同時當執行緒都完成后也會觸發事件,以便進行后面的操作, 這個時候就可以使用CountDownLatch,
CyclicBarrier 使用場景
CyclicBarrier可以用于多執行緒計算資料,最后合并計算結果的應用場景,
12.CyclicBarrier和CountDownLatch的區別
1)CountDownLatch簡單的說就是一個執行緒等待,直到他所等待的其他執行緒都執行完成并且呼叫countDown()方法發出通知后,當前執行緒才可以繼續執行,
2)cyclicBarrier是所有執行緒都進行等待,直到所有執行緒都準備好進入await()方法之后,所有執行緒同時開始執行!
3)CountDownLatch的計數器只能使用一次,而CyclicBarrier的計數器可以使用reset() 方法重置,所以CyclicBarrier能處理更為復雜的業務場景,比如如果計算發生錯誤,可以重置計數器,并讓執行緒們重新執行一次,
4)CyclicBarrier還提供其他有用的方法,比如getNumberWaiting方法可以獲得CyclicBarrier阻塞的執行緒數量,isBroken方法用來知道阻塞的執行緒是否被中斷,如果被中斷回傳true,否則回傳false,
13.synchronized的作用?
在Java中,synchronized關鍵字是用來控制執行緒同步的,就是在多執行緒的環境下,控制synchronized代碼段不被多個執行緒同時執行,
synchronized既可以加在一段代碼上,也可以加在方法上,
14.volatile關鍵字的作用
對于可見性,Java提供了volatile關鍵字來保證可見性,
當一個共享變數被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他執行緒需要讀取時,它會去記憶體中讀取新值,
從實踐角度而言,volatile的一個重要作用就是和CAS結合,保證了原子性,詳細的可以參見java.util.concurrent.atomic包下的類,比如AtomicInteger,
15.什么是CAS
CAS是compare and swap的縮寫,即我們所說的比較交換,
cas是一種基于鎖的操作,而且是樂觀鎖,在java中鎖分為樂觀鎖和悲觀鎖,悲觀鎖是將資源鎖住,等一個之前獲得鎖的執行緒釋放鎖之后,下一個執行緒才可以訪問,而樂觀鎖采取了一種寬泛的態度,通過某種方式不加鎖來處理資源,比如通過給記錄加version來獲取資料,性能較悲觀鎖有很大的提高,
CAS
操作包含三個運算元 ——
記憶體位置(V)、預期原值(A)和新值(B),如果記憶體地址里面的值和A的值是一樣的,那么就將記憶體里面的值更新成B,CAS是通過無限回圈來獲取資料的,若果在第一輪回圈中,a執行緒獲取地址里面的值被b執行緒修改了,那么a執行緒需要自旋,到下次回圈才有可能機會執行,
java.util.concurrent.atomic 包下的類大多是使用CAS操作來實作的( AtomicInteger,AtomicBoolean,AtomicLong),
16. CAS的問題
1)CAS容易造成ABA問題,一個執行緒a將數值改成了b,接著又改成了a,此時CAS認為是沒有變化,其實是已經變化過了,而這個問題的解決方案可以使用版本號標識,每操作一次version加1,在java5中,已經提供了AtomicStampedReference來解決問題,
2) 不能保證代碼塊的原子性
CAS機制所保證的知識一個變數的原子性操作,而不能保證整個代碼塊的原子性,比如需要保證3個變數共同進行原子性的更新,就不得不使用synchronized了,
3)CAS造成CPU利用率增加,之前說過了CAS里面是一個回圈判斷的程序,如果執行緒一直沒有獲取到狀態,cpu資源會一直被占用,
17.什么是Future?
在并發編程中,我們經常用到非阻塞的模型,在之前的多執行緒的三種實作中,不管是繼承thread類還是實作runnable介面,都無法保證獲取到之前的執行結果,通過實作Callback介面,并用Future可以來接收多執行緒的執行結果,
Future表示一個可能還沒有完成的異步任務的結果,針對這個結果可以添加Callback以便在任務執行成功或失敗后作出相應的操作,
18.什么是AQS
AQS是AbustactQueuedSynchronizer的簡稱,它是一個Java提高的底層同步工具類,用一個int型別的變數表示同步狀態,并提供了一系列的CAS操作來管理這個同步狀態,
AQS是一個用來構建鎖和同步器的框架,使用AQS能簡單且高效地構造出應用廣泛的大量的同步器,比如我們提到的ReentrantLock,Semaphore,其他的諸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的,
AQS支持兩種同步方式:
1.獨占式
2.共享式
這樣方便使用者實作不同型別的同步組件,獨占式如ReentrantLock,共享式如Semaphore,CountDownLatch,組合式的如ReentrantReadWriteLock,總之,AQS為使用提供了底層支撐,如何組裝實作,使用者可以自由發揮,
19.ReadWriteLock是什么
首先明確一下,不是說ReentrantLock不好,只是ReentrantLock某些時候有局限,如果使用ReentrantLock,可能本身是為了防止執行緒A在寫資料、執行緒B在讀資料造成的資料不一致,但這樣,如果執行緒C在讀資料、執行緒D也在讀資料,讀資料是不會改變資料的,沒有必要加鎖,但是還是加鎖了,降低了程式的性能,
因為這個,才誕生了讀寫鎖ReadWriteLock,ReadWriteLock是一個讀寫鎖介面,ReentrantReadWriteLock是ReadWriteLock介面的一個具體實作,實作了讀寫的分離,讀鎖是共享的,寫鎖是獨占的,讀和讀之間不會互斥,讀和寫、寫和讀、寫和寫之間才會互斥,提升了讀寫的性能,
20.FutureTask是什么
這個其實前面有提到過,FutureTask表示一個異步運算的任務,FutureTask里面可以傳入一個Callable的具體實作類,可以對這個異步運算的任務的結果進行等待獲取、判斷是否已經完成、取消任務等操作,當然,由于FutureTask也是Runnable介面的實作類,所以FutureTask也可以放入執行緒池中,
21.synchronized和ReentrantLock的區別
synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質區別,既然ReentrantLock是類,那么它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變數,ReentrantLock比synchronized的擴展性體現在幾點上:
(1)ReentrantLock可以對獲取鎖的等待時間進行設定,這樣就避免了死鎖
(2)ReentrantLock可以獲取各種鎖的資訊
(3)ReentrantLock可以靈活地實作多路通知
另外,二者的鎖機制其實也是不一樣的,ReentrantLock底層呼叫的是Unsafe的park方法加鎖,synchronized操作的應該是物件頭中mark word,這點我不能確定,
22.什么是樂觀鎖和悲觀鎖
(1)樂觀鎖:就像它的名字一樣,對于并發間操作產生的執行緒安全問題持樂觀狀態,樂觀鎖認為競爭不總是會發生,因此它不需要持有鎖,將比較-替換這兩個動作作為一個原子操作嘗試去修改記憶體中的變數,如果失敗則表示發生沖突,那么就應該有相應的重試邏輯,
(2)悲觀鎖:還是像它的名字一樣,對于并發間操作產生的執行緒安全問題持悲觀狀態,悲觀鎖認為競爭總是會發生,因此每次對某資源進行操作時,都會持有一個獨占的鎖,就像synchronized,不管三七二十一,直接上了鎖就操作資源了,
23.執行緒B怎么知道執行緒A修改了變數
- volatile修飾變數
- synchronized修飾修改變數的方法
- wait/notify
- while輪詢
24.synchronized、volatile、CAS比較
- synchronized是悲觀鎖,屬于搶占式,會引起其他執行緒阻塞,
- volatile提供多執行緒共享變數可見性和禁止指令重排序優化,
- CAS是基于沖突檢測的樂觀鎖(非阻塞)
25.sleep方法和wait方法有什么區別?
這個問題常問,sleep方法和wait方法都可以用來放棄CPU一定的時間,不同點在于如果執行緒持有某個物件的監視器,sleep方法不會放棄這個物件的監視器,wait方法會放棄這個物件的監視器
26.ThreadLocal是什么?有什么用?
ThreadLocal是一個本地執行緒副本變數工具類,主要用于將私有執行緒和該執行緒存放的副本物件做一個映射,各個執行緒之間的變數互不干擾,在高并發場景下,可以實作無狀態的呼叫,特別適用于各個執行緒依賴不通的變數值完成操作的場景,
簡單說ThreadLocal就是一種以空間換時間的做法,在每個Thread里面維護了一個以開地址法實作的ThreadLocal.ThreadLocalMap,把資料進行隔離,資料不共享,自然就沒有執行緒安全方面的問題了,
27.為什么wait()方法和notify()/notifyAll()方法要在同步塊中被呼叫
這是JDK強制的,wait()方法和notify()/notifyAll()方法在呼叫前都必須先獲得物件的鎖
28.多執行緒同步有哪幾種方法?
Synchronized關鍵字,Lock鎖實作,分布式鎖等,
29.執行緒的調度策略
執行緒調度器選擇優先級最高的執行緒運行,但是,如果發生以下情況,就會終止執行緒的運行:
(1)執行緒體中呼叫了yield方法讓出了對cpu的占用權利
(2)執行緒體中呼叫了sleep方法使執行緒進入睡眠狀態
(3)執行緒由于IO操作受到阻塞
(4)另外一個更高優先級執行緒出現
(5)在支持時間片的系統中,該執行緒的時間片用完
30.ConcurrentHashMap的并發度是什么
ConcurrentHashMap的并發度就是segment的大小,默認為16,這意味著最多同時可以有16條執行緒操作ConcurrentHashMap,這也是ConcurrentHashMap對Hashtable的最大優勢,任何情況下,Hashtable能同時有兩條執行緒獲取Hashtable中的資料嗎?
31.Java死鎖以及如何避免?
Java中的死鎖是一種編程情況,其中兩個或多個執行緒被永久阻塞,Java死鎖情況出現至少兩個執行緒和兩個或更多資源,
Java發生死鎖的根本原因是:在申請鎖時發生了交叉倍訓申請,
死鎖的原因
1)是多個執行緒涉及到多個鎖,這些鎖存在著交叉,所以可能會導致了一個鎖依賴的倍訓,
例如:執行緒在獲得了鎖A并且沒有釋放的情況下去申請鎖B,這時,另一個執行緒已經獲得了鎖B,在釋放鎖B之前又要先獲得鎖A,因此倍訓發生,陷入死鎖回圈,
2)默認的鎖申請操作是阻塞的,
所以要避免死鎖,就要在一遇到多個物件鎖交叉的情況,就要仔細審查這幾個物件的類中的所有方法,是否存在著導致鎖依賴的環路的可能性, 總之是盡量避免在一個同步方法中呼叫其它物件的延時方法和同步方法,
32.怎么喚醒一個阻塞的執行緒
如果執行緒是因為呼叫了wait()、sleep()或者join()方法而導致的阻塞,可以中斷執行緒,并且通過拋出InterruptedException來喚醒它;如果執行緒遇到了IO阻塞,無能為力,因為IO是作業系統實作的,Java代碼并沒有辦法直接接觸到作業系統,
33.不可變物件對多執行緒有什么幫助
前面有提到過的一個問題,不可變物件保證了物件的記憶體可見性,對不可變物件的讀取不需要進行額外的同步手段,提升了代碼執行效率,
34.什么是多執行緒的背景關系切換
多執行緒的背景關系切換是指CPU控制權由一個已經正在運行的執行緒切換到另外一個就緒并等待獲取CPU執行權的執行緒的程序,
35.如果你提交任務時,執行緒池佇列已滿,這時會發生什么
這里區分一下:
- 如果使用的是無界佇列LinkedBlockingQueue,也就是無界佇列的話,沒關系,繼續添加任務到阻塞佇列中等待執行,因為LinkedBlockingQueue可以近乎認為是一個無窮大的佇列,可以無限存放任務
- 如果使用的是有界佇列比如ArrayBlockingQueue,任務首先會被添加到ArrayBlockingQueue中,ArrayBlockingQueue滿了,會根據maximumPoolSize的值增加執行緒數量,如果增加了執行緒數量還是處理不過來,ArrayBlockingQueue繼續滿,那么則會使用拒絕策略RejectedExecutionHandler處理滿了的任務,默認是AbortPolicy
36.Java中用到的執行緒調度演算法是什么
搶占式,一個執行緒用完CPU之后,作業系統會根據執行緒優先級、執行緒饑餓情況等資料算出一個總的優先級并分配下一個時間片給某個執行緒執行,
37.什么是執行緒調度器(Thread Scheduler)和時間分片(Time Slicing)?
執行緒調度器是一個作業系統服務,它負責為Runnable狀態的執行緒分配CPU時間,一旦我們創建一個執行緒并啟動它,它的執行便依賴于執行緒調度器的實作,時間分片是指將可用的CPU時間分配給可用的Runnable執行緒的程序,分配CPU時間可以基于執行緒優先級或者執行緒等待的時間,執行緒調度并不受到Java虛擬機控制,所以由應用程式來控制它是更好的選擇(也就是說不要讓你的程式依賴于執行緒的優先級),
38.Java Concurrency API中的Lock介面(Lock interface)是什么?對比同步它有什么優勢?
Lock介面比同步方法和同步塊提供了更具擴展性的鎖操作,他們允許更靈活的結構,可以具有完全不同的性質,并且可以支持多個相關類的條件物件,
它的優勢有:
- 可以使鎖更公平
- 可以使執行緒在等待鎖的時候回應中斷
- 可以讓執行緒嘗試獲取鎖,并在無法獲取鎖的時候立即回傳或者等待一段時間
- 可以在不同的范圍,以不同的順序獲取和釋放鎖
39.單例模式的執行緒安全性
老生常談的問題了,首先要說的是單例模式的執行緒安全意味著:某個類的實體在多執行緒環境下只會被創建一次出來,單例模式有很多種的寫法,我總結一下:
(1)餓漢式單例模式的寫法:執行緒安全
(2)懶漢式單例模式的寫法:非執行緒安全
(3)雙檢鎖單例模式的寫法:執行緒安全
40.Semaphore有什么作用
Semaphore就是一個信號量,它的作用是限制某段代碼塊的并發數,Semaphore有一個建構式,可以傳入一個int型整數n,表示某段代碼最多只有n個執行緒可以訪問,如果超出了n,那么請等待,等到某個執行緒執行完畢這段代碼塊,下一個執行緒再進入,由此可以看出如果Semaphore建構式中傳入的int型整數n=1,相當于變成了一個synchronized了,
41.Executors類是什么?
Executors為Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable類提供了一些工具方法,
Executors可以用于方便的創建執行緒池
42.執行緒類的構造方法、靜態塊是被哪個執行緒呼叫的
這是一個非常刁鉆和狡猾的問題,請記住:執行緒類的構造方法、靜態塊是被new這個執行緒類所在的執行緒所呼叫的,而run方法里面的代碼才是被執行緒自身所呼叫的,
如果說上面的說法讓你感到困惑,那么我舉個例子,假設Thread2中new了Thread1,main函式中new了Thread2,那么:
(1)Thread2的構造方法、靜態塊是main執行緒呼叫的,Thread2的run()方法是Thread2自己呼叫的
(2)Thread1的構造方法、靜態塊是Thread2呼叫的,Thread1的run()方法是Thread1自己呼叫的
43.同步方法和同步塊,哪個是更好的選擇
同步塊,這意味著同步塊之外的代碼是異步執行的,這比同步整個方法更提升代碼的效率,請知道一條原則:同步的范圍越小越好,
44.Java執行緒數過多會造成什么例外?
1)執行緒的生命周期開銷非常高
2)消耗過多的CPU資源
如果可運行的執行緒數量多于可用處理器的數量,那么有執行緒將會被閑置,大量空閑的執行緒會占用許多記憶體,給垃圾回收器帶來壓力,而且大量的執行緒在競爭CPU資源時還將產生其他性能的開銷,
3)降低穩定性
JVM在可創建執行緒的數量上存在一個限制,這個限制值將隨著平臺的不同而不同,并且承受著多個因素制約,包括JVM的啟動引數、Thread建構式中請求堆疊的大小,以及底層作業系統對執行緒的限制等,如果破壞了這些限制,那么可能拋出OutOfMemoryError例外,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/10577.html
標籤:其他
上一篇:面試官教你如何高效碼簡歷
