一.List相關面試題
List是一個集合接口,分爲AarrayList和LinkedList兩個實現方式
**ArrayList:**底層實現就是數組,且ArrayList實現了RandomAccess,表示它能快速隨機訪問存儲的元素,通過下標
index
訪問,只是我們需要用get()
方法的形式, 數組支持隨機訪問, 查詢速度快, 增刪元素慢;**LinkdeList:**底層實現是鏈表,
LinkedList
沒有實現RandomAccess
接口,鏈表支持順序訪問, 查詢速度慢, 增刪元素快
ArrayList實現線程安全
List list = Collections.synchronizedList(new ArrayList());
二.Map相關
1.你都知道哪些常用的Map集合?
HashMap、HashTable、LinkedHashMap、ConcurrentHashMap。
2.Collection集合接口和Map接口有什麼關係?
沒關係,Collection是List、Set父接口不是Map父接口
3.HashMap是線程安全的嗎?線程安全的Map都有哪些?性能最好的是哪個?
HashMap不是線程安全的。線程安全的有HashTable、ConcurrentHashMap、SynchronizedMap,性能最好的是ConcurrentHashMap。
4.使用HashMap有什麼性能問題嗎?
使用HashMap要注意避免集合的擴容,它會很耗性能,根據元素的數量給它一個初始大小的值。
5.HashMap的數據結構是怎樣的?默認大小是多少?內部是怎麼擴容的?
HashMap是數組和鏈表組成的,默認大小爲16,當hashmap中的元素個數超過數組大小**loadFactor(默認值爲0.75)**時就會把數組的大小擴展爲原來的兩倍大小,然後重新計算每個元素在數組中的位置。
6.怎麼按添加順序存儲元素?怎麼按A-Z自然順序存儲元素?怎麼自定義排序?
按添加順序使用LinkedHashMap,按自然順序使用TreeMap,自定義排序TreeMap(Comparetor c)。
7.HashMap的鏈表結構設計是用來解決什麼問題的?
HashMap的鏈表結構設計是用來解決key的hash衝突問題的。
8.HashMap的鍵、值可以爲NULL嗎?HashTable呢?
HashMap的鍵值都可以爲NULL,HashTable不行。
9.HashMap使用對象作爲key,如果hashcode相同會怎麼處理?
key的hash衝突,如果key equals一致將會覆蓋值,不一致就會將值存儲在key對應的鏈表中。
10.HashMap中的get操作是什麼原理?
先根據key的hashcode值找到對應的鏈表,再循環鏈表,根據key的hash是否相同且key的==或者equals比較操作找到對應的值。
11.HashMap中的put操作時什麼原理
hash(key)
,取key的hashcode進行高位運算,返回hash值- 如果hash數組爲空,直接
resize()
(調整大小)- 對hash進行取模運算計算,得到key-value在數組中的存儲位置i
- 如果
table[i] == null
,直接插入Node<key,value>
- 如果
table[i] != null
,判斷是否爲紅黑樹p instanceof TreeNode
。- 如果是紅黑樹,則判斷TreeNode是否已存在,如果存在則直接返回oldnode並更新;不存在則直接插入紅黑樹,
++size
,超出threshold容量就擴容- 如果是鏈表,則判斷Node是否已存在,如果存在則直接返回oldnode並更新;不存在則直接插入鏈表尾部,判斷鏈表長度,如果大於8則轉爲紅黑樹存儲,
++size
,超出threshold容量就擴容
三、線程相關
1.什麼是線程
線程是操作系統能夠進行運算調度的最小單位,它被包含在進程之中,是進程中的實際運作單位。程序員可以通過它進行多處理器編程,你可以使用多線程對 運算密集型任務提速。比如,如果一個線程完成一個任務要100毫秒,那麼用十個線程完成改任務只需10毫秒。
2.線程和進程有什麼區別?
線程是進程的子集,一個進程可以有很多線程,每條線程並行執行不同的任務。不同的進程使用不同的內存空間,而所有的線程共享一片相同的內存空間。別把它和棧內存搞混,每個線程都擁有單獨的棧內存用來存儲本地數據。
3.如何在Java中實現線程?
在語言層面有兩種方式。java.lang.Thread 類的實例就是一個線程但是它需要調用java.lang.Runnable接口來執行,由於線程類本身就是調用的Runnable接口所以你可以繼承 java.lang.Thread 類或者直接調用Runnable接口來重寫run()方法實現線程。
4.用Runnable還是Thread?
這個問題是上題的後續,大家都知道我們可以通過繼承Thread類或者調用Runnable接口來實現線程,問題是,那個方法更好呢?什麼情況下使 用它?這個問題很容易回答,如果你知道Java不支持類的多重繼承,但允許你調用多個接口。所以如果你要繼承其他類,當然是調用Runnable接口好 了。
5.Thread 類中的start() 和 run() 方法有什麼區別?
這個問題經常被問到,但還是能從此區分出面試者對Java線程模型的理解程度。start()方法被用來啓動新創建的線程,而且start()內部 調用了run()方法,這和直接調用run()方法的效果不一樣。當你調用run()方法的時候,只會是在原來的線程中調用,沒有新的線程啓 動,start()方法纔會啓動新線程。
6.Java中Runnable和Callable有什麼不同?
Runnable和Callable都代表那些要在不同的線程中執行的任務。Runnable從JDK1.0開始就有了,Callable是在 JDK1.5增加的。它們的主要區別是Callable的 call() 方法可以返回值和拋出異常,而Runnable的run()方法沒有這些功能。Callable可以返回裝載有計算結果的Future對象。
7.ava中CyclicBarrier 和 CountDownLatch有什麼不同?
CyclicBarrier 和 CountDownLatch 都可以用來讓一組線程等待其它線程。與 CyclicBarrier 不同的是,CountdownLatch 不能重新使用。
8.Java 線程規則模型是什麼?
Java內存模型規定和指引Java程序在不同的內存架構、CPU和操作系統間有確定性地行爲。它在多線程的情況下尤其重要。Java內存模型對一 個線程所做的變動能被其它線程可見提供了保證,它們之間是先行發生關係。這個關係定義了一些規則讓程序員在併發編程時思路更清晰。比如,先行發生關係確保 了:
- 線程內的代碼能夠按先後順序執行,這被稱爲程序次序規則。
- 對於同一個鎖,一個解鎖操作一定要發生在時間上後發生的另一個鎖定操作之前,也叫做管程鎖定規則。
- 前一個對
volatile
的寫操作在後一個volatile
的讀操作之前,也叫volatile
變量規則。- 一個線程內的任何操作必需在這個線程的start()調用之後,也叫作線程啓動規則。
- 一個線程的所有操作都會在線程終止之前,線程終止規則。
- 一個對象的終結操作必需在這個對象構造完成之後,也叫對象終結規則。
- 可傳遞性
9.Java中的volatile 變量是什麼?
volatile是一個特殊的修飾符,只有成員變量才能使用它。在Java併發程序缺少同步類的情況下,多線程對成員變量的操作對其它線程是透明的。volatile變量可以保證下一個讀取操作會在前一個寫操作之後發生,就是上一題的volatile變量規則。
10.Java中如何停止一個線程
Java提供了很豐富的API但沒有爲停止線程提供API。JDK 1.0本來有一些像stop(), suspend() 和 resume()的控制方法但是由於潛在的死鎖威脅因此在後續的JDK版本中他們被棄用了,之後Java API的設計者就沒有提供一個兼容且線程安全的方法來停止一個線程。當run() 或者 call() 方法執行完的時候線程會自動結束,如果要手動結束一個線程,你可以用volatile 布爾變量來退出run()方法的循環或者是取消任務來中斷線程。
Thread類提供了一個線程終止的方法stop()方法,但是現在在JDK源碼中發現,stop()方法已經被廢棄。主要原因是:stop()方法太過暴力。強制終止一個正在執行的線程。這樣的話會造成一些數據不一致的問題。
public void Thread.interrupt() //中斷線程 public boolean Thread.isInterrupted() //判斷線程是否中斷 public static boolean Thread.interrupted() //判斷是否被中斷,並清除當前中斷狀態
Thread.interrupt()方法是一個實例方法,它通知目標線程中斷,也是設置中斷標誌位。中斷標誌位表示當前線程已經被中斷了。
Thread.isInterrupted() 方法也是實例方法,主要是檢查當前線程是否被中斷(通過檢查中斷標誌位),返回值是boolean類型。
Thread.interrupted() 方法也是用來判斷當前線程是否被中斷,但同時清除當前線程的中斷標誌位狀態。
使用Thread.interrupt()方法處理現場中斷,需要使用Thread.isInterrupted()判斷線程是否被中斷,然後進入中斷處理邏輯代碼。
11.一個線程運行時發生異常會怎樣?
如果異常沒有被捕獲該線程將會停止執行。Thread.UncaughtExceptionHandler是用於處理未捕獲異常造成線程突然中 斷情況的一個內嵌接口。當一個未捕獲異常將造成線程中斷的時候JVM會使用Thread.getUncaughtExceptionHandler()來 查詢線程的UncaughtExceptionHandler並將線程和異常作爲參數傳遞給handler的uncaughtException()方法 進行處理。
12.如何在兩個線程間共享數據?
你可以通過共享對象來實現這個目的,或者是使用像阻塞隊列這樣併發的數據結構。
用wait和notify方法實現了生產者消費者模型。
13.Java中notify 和 notifyAll有什麼區別?
多線程可以等待單監控鎖,Java API 的設計人員提供了一些方法當等待條件改變的時候通知它們,但是這些方法沒有完全實現。notify()方法不能喚醒某個具體的線程,所以只有一個線程在等 待的時候它纔有用武之地。而notifyAll()喚醒所有線程並允許他們爭奪鎖確保了至少有一個線程能繼續運行。
14.爲什麼wait, notify 和 notifyAll這些方法不在thread類裏面
這是個設計相關的問題,它考察的是面試者對現有系統和一些普遍存在但看起來不合理的事物的看法。回答這些問題的時候,你要說明爲什麼把這些方法放在 Object類裏是有意義的,還有不把它放在Thread類裏的原因。一個很明顯的原因是JAVA提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通 過線程獲得。如果線程需要等待某些鎖那麼調用對象中的wait()方法就有意義了。如果wait()方法定義在Thread類中,線程正在等待的是哪個鎖 就不明顯了。簡單的說,由於wait,notify和notifyAll都是鎖級別的操作,所以把他們定義在Object類中因爲鎖屬於對象。
15.什麼是ThreadLocal變量
ThreadLocal是Java裏一種特殊的變量。每個線程都有一個ThreadLocal就是每個線程都擁有了自己獨立的一個變量,競爭條件被 徹底消除了。它是爲創建代價高昂的對象獲取線程安全的好方法,比如你可以用ThreadLocal讓SimpleDateFormat變成線程安全的,因 爲那個類創建代價高昂且每次調用都需要創建不同的實例所以不值得在局部範圍使用它,如果爲每個線程提供一個自己獨有的變量拷貝,將大大提高效率。首先,通 過複用減少了代價高昂的對象的創建個數。其次,你在沒有使用高代價的同步或者不變性的情況下獲得了線程安全。線程局部變量的另一個不錯的例子是 ThreadLocalRandom類,它在多線程環境中減少了創建代價高昂的Random對象的個數。
16.什麼是FutureTask
在Java併發程序中FutureTask表示一個可以取消的異步運算。它有啓動和取消運算、查詢運算是否完成和取回運算結果等方法。只有當運算完 成的時候結果才能取回,如果運算尚未完成get方法將會阻塞。一個FutureTask對象可以對調用了Callable和Runnable的對象進行包 裝,由於FutureTask也是調用了Runnable接口所以它可以提交給Executor來執行。可以設置時間來控制一個超時任務。
17.爲什麼wait和notify方法要在同步塊中調用?
主要是因爲Java API強制要求這樣做,如果你不這麼做,你的代碼會拋出IllegalMonitorStateException異常。還有一個原因是爲了避免wait和notify之間產生競態條件。
18.如何寫代碼來解決生產者消費者問題?
在現實中你解決的許多線程問題都屬於生產者消費者模型,就是一個線程生產任務供其它線程進行消費,你必須知道怎麼進行線程間通信來解決這個問題。比 較低級的辦法是用wait和notify來解決這個問題,比較讚的辦法是用Semaphore 或者 BlockingQueue來實現生產者消費者模型;
19.Thread類中的yield方法有什麼作用?
Yield方法可以暫停當前正在執行的線程對象,讓其它有相同優先級的線程執行。它是一個靜態方法而且只保證當前線程放棄CPU佔用而不能保證使其它線程一定能佔用CPU,執行yield()的線程有可能在進入到暫停狀態後馬上又被執行。
20. Java多線程中調用wait() 和 sleep()方法有什麼不同?
Java程序中wait 和 sleep都會造成某種形式的暫停,它們可以滿足不同的需要。wait()方法用於線程間通信,如果等待條件爲真且其它線程被喚醒時它會釋放鎖,而 sleep()方法僅僅釋放CPU資源或者讓當前線程停止執行一段時間,但不會釋放鎖。
三、JVM相關
1.JVM運行時數據區包含哪些?
程序計數器:行號指示器,通過改變該值,以選取下一步的指令
Java虛擬機棧:局部變量、方法出口等,爲JVM服務
本地方法棧:局部變量、方法出口等,爲本地Native方法服務
堆區:內存最大的一塊,所有的對象實例都在這裏分配內存
方法區:常量、靜態變量等
2.JVM的主要組成部分及其作用?
類加載器 ClassLoader:Java代碼 -----> 字節碼 的編譯過程
運行時數據區:把上一步編譯得到的字節碼加載到內存中
執行引擎:命令解析器,解析上一步加載而來的字節碼,翻譯成爲系統指令,交由CPU執行
本地庫接口 Native Interface:諸如IO之類的由其他語言寫成的本地庫接口
3.堆棧的區別
功能方面:堆存放對象,棧執行程序。
共享性:堆全線程可見,棧線程私有。
空間大小:堆內存遠大於棧內存。
4.隊列和棧的定義?以及區別?
預存儲數據的一種數據結構。
隊列先進先出:FIFO
棧後進先出:LIFO
5.雙親委派模型
如果一個類加載器收到了類加載的請求,它首先不會自己去加載這個類,而是把這個請求委派給父類加載器去完成,每一層的類加載器都是如此,這樣所有的加載請求都會被傳送到頂層的啓動類加載器中,只有當父加載無法完成加載請求(它的搜索範圍中沒找到所需的類)時,子加載器纔會嘗試去加載類。
加載器的順序(先從下到上,再從上到下):
- 啓動類加載器:JVM的一部分,加載JAVA_HOME/lib目錄下的類,或者是被-Xbootclasspath指定的類路徑
- 擴展類加載器:負責加載JAVA_HOME/lib/ext目錄下的類,或者是被java.ext.dirs指定的類路徑
- 程序類加載器:負責加載classpath裏的類。
6.每個ClassLoader加載Class的過程是
1.檢測此Class是否載入過(即在cache中是否有此Class),如果有到8,如果沒有到2
2.如果parent classloader不存在(沒有parent,那parent一定是bootstrap classloader了),到4
3.請求parent classloader載入,如果成功到8,不成功到5
4.請求jvm從bootstrap classloader中載入,如果成功到8
5.尋找Class文件(從與此classloader相關的類路徑中尋找)。如果找不到則到7.
6.從文件中載入Class,到8.
7.拋出ClassNotFoundException.
8.返回Class.
7.類裝載的執行過程
1.加載:加載class文件到內存中
2.檢查:檢查class文件有無錯誤
3.準備:靜態變量分配內存空間
4.解析:靜態變量的引用指向內存地址
5.初始化:初始化成員變量等…
8.判斷對象可回收
- 引用計數法:被引用,則計數器+1,當計數器爲0時,回收對象。
- 可達性分析:從當前對象到GC Root,沒有任何引用鏈時,該對象即可回收。
9.Java中都有哪些引用類型
強引用:發生gc時不會被回收
軟引用:有用但不是必須的,內存溢出時會被清理
弱引用:有用但不是必須的,下次gc時會被清理
虛引用:無法通過虛引用獲得對象,用 PhantomReference 實現虛引用,虛引用的用途是在 gc 時返回一個通知。
10.JVM的垃圾回收算法
- 標記-清除:無用對象全部幹掉
- 標記-整理:有用對象都向一邊移動,邊界以外的全部幹掉
- 複製算法:左邊內存快滿時,將其中要保留的對象複製到右邊內存中,然後整體幹掉左邊內存。右邊同理,內存利用率僅有一半
- 分代算法:根據對象存活週期的不同將內存劃分爲幾塊,一般是新生代和老年代,新生代基本採用複製算法,老年代採用標記整理算法
11.JVM的垃圾回收器
CMS:一種以獲得最短停頓時間爲目標的收集器,非常適用 B/S 系統。
G1:一種兼顧吞吐量和停頓時間的 GC 實現,是 JDK 9 以後的默認 GC 選項。
12.CMS垃圾回收器
犧牲吞吐量爲代價,獲得最短的回收停頓時間。
要求服務器響應速度額應用上,最爲適合。常用於B/S架構。
採用標記-清除算法實現,剩餘內存無法滿足要求時,會出現“Concurrent Mode Failure”,並採用Serial Old來單線程回收老年代內存。
解決和避免“Concurrent Mode Failure”的方法如下:
調小年輕代,適當調整老年代的回收閾值和GC頻次,以保證年輕代數據置入過來時有足夠空間可用。
13.新生代垃圾回收器和老生代垃圾回收器都有哪些?有什麼區別?
• 新生代回收器: Serial、 ParNew、 Parallel Scavenge
• 老年代回收器: Serial Old、 Parallel Old、 CMS
• 整堆回收器: G1
新生代垃圾回收器一般採用的是複製算法,複製算法的優點是效率高,缺點是內存利用率低;
老年代回收器一般採用的是標記-整理的算法進行垃圾回收。
14.簡述分代垃圾回收器是怎麼工作的?
分代回收器有兩個分區:老生代和新生代,新生代默認的空間佔比總空間的 1/3,老生代的默認佔比是 2/3。
新生代使用的是複製算法,新生代裏有 3 個分區: Eden、 To Survivor、 From Survivor,它們的默認佔比是 8:1:1,它的執行流程如下:
• 把 Eden + From Survivor 存活的對象放入 To Survivor 區;
• 清空 Eden 和 From Survivor 分區;
• From Survivor 和 To Survivor 分區交換, From Survivor 變 To Survivor, To Survivor 變 From Survivor。
每次在 From Survivor 到 To Survivor 移動時都存活的對象,年齡就 +1,當年齡到達 15(默認配置是 15)時,升級爲老生代。大對象也會直接進入老生代。
老生代當空間佔用到達某個值之後就會觸發全局垃圾收回,一般使用標記整理的執行算法。以上這些循環往復就構成了整個分代垃圾回收的整體執行流程。
15.JVM 調優的工具?
JDK 自帶了很多監控工具,都位於 JDK 的 bin 目錄下,其中最常用的是 jconsole 和 jvisualvm 這兩款視圖監控工具。
• jconsole:用於對 JVM 中的內存、線程和類等進行監控;
• jvisualvm: JDK 自帶的全能分析工具,可以分析:內存快照、線程快照、程序死鎖、監控內存的變化、 gc 變化等。
四、JAVA 鎖相關
1.悲觀鎖
悲觀鎖在修改整個過程中保持對修改數據的加鎖,一直到修改結束,防止其它線程或者進程對數據修改。
悲觀鎖適用於寫多讀少的情況下,技術實現上依賴數據庫的鎖機制實現,保證最大程度的獨佔性。
常用select for update,進行加鎖,並且取消事務的自動提交,在修改之後其它事務請求才可以修改數據,其間,select from只讀操作是可以進行的。
2.樂觀鎖
分爲三個階段:數據讀取、寫入校驗、數據寫入。
假設數據一般情況下不會造成衝突,只有在數據進行提交更新時,纔會正式對數據的衝突與否進行檢測,如果發現衝突了,則返回錯誤信息,讓用戶決定如何去做。fail-fast機制
樂觀鎖在適用於讀多寫少的情況下;常見的就是增加verson字段進行樂觀鎖控制。
3.利用緩存實現分佈式鎖
利用緩存的某些原子特性實現分佈式鎖,關鍵在於原子性操作,比如redis的setnx操作,當然需要優化,可能面臨key在主從切換時丟失的問題,即使增加超時設置。官方推薦的redission lock可以解決這個問題。
4.Redisson實現Redis分佈式鎖的底層原理
redisson 是 redis 官方推薦的 java 語言實現分佈式鎖的項目。該項目在基於 netty 框架的基礎上提供了一系列分佈式特性的工具類。下面我們來使用 Redisson 實現分佈式鎖。
在Redisson中,使用key來作爲是否上鎖的標誌,當通過getLock(String key)方法獲得相應的鎖之後,這個key即作爲一個鎖存儲到Redis集羣中,在接下來如果有其他的線程嘗試獲取名爲key的鎖時,便會向集羣中進行查詢,如果能夠查到這個鎖並發現相應的value的值不爲0,則表示已經有其他線程申請了這個鎖同時還沒有釋放,則當前線程進入阻塞,否則由當前線程獲取這個鎖並將value值加一,如果是可重入鎖的話,則當前線程每獲得一個自身線程的鎖,就將value的值加一,而每釋放一個鎖則將value值減一,直到減至0,完全釋放這個鎖。因爲底層是基於分佈式的Redis集羣,所以Redisson實現了分佈式的鎖機制。
設置鎖的超時時間,是爲了防止死鎖
Redisson 默認的 CommandExecutor 實現是通過 eval 命令來執行 Lua 腳本,所以要求 Redis 的版本必須爲 2.6 或以上,否則可能要自己來實現
5.可重入鎖的理解
1) 舉個例子
在一個村子裏面,有一口井水,水質非常的好,村民們都想打井裏的水。這井只有一口,村裏的人那麼多,所以得出個打水的規則纔行。村長絞盡腦汁,最終想出了一個比較合理的方案,咱們來仔細的看看聰明的村長大人的智慧。
井邊安排一個看井人,維護打水的秩序。
打水時,以家庭爲單位,哪個家庭任何人先到井邊,就可以先打水,而且如果一個家庭佔到了打水權,其家人這時候過來打水不用排隊。而那些沒有搶佔到打水權的人,一個一個挨着在井邊排成一隊,先到的排在前面。打水示意圖如下 :
![打水示意圖](…/image/
)
是不是感覺很和諧,如果打水的人打完了,他會跟看井人報告,看井人會讓第二個人接着打水。這樣大家總都能夠打到水。是不是看起來挺公平的,先到的人先打水,當然不是絕對公平的,自己看看下面這個場景 :
看着,一個有娃的父親正在打水,他的娃也到井邊了,所以女憑父貴直接排到最前面打水,羨煞旁人了。
以上這個故事模型就是所謂的公平鎖模型,當一個人想到井邊打水,而現在打水的人又不是自家人,這時候就得乖乖在隊列後面排隊。事情總不是那麼一帆風順的,總會有些人想走捷徑,話說看井人年紀大了,有時候,眼力不是很好,這時候,人們開始打起了新主意。新來打水的人,他們看到有人排隊打水的時候,他們不會那麼乖巧的就排到最後面去排隊,反之,他們會看看現在有沒有人正在打水,如果有人在打水,沒輒了,只好排到隊列最後面,但如果這時候前面打水的人剛剛打完水,正在交接中,排在隊頭的人還沒有完成交接工作,這時候,新來的人可以嘗試搶打水權,如果搶到了,呵呵,其他人也只能睜一隻眼閉一隻眼,因爲大家都默認這個規則了。這就是所謂的非公平鎖模型。新來的人不一定總得乖乖排隊,這也就造成了原來隊列中排隊的人可能要等很久很久。
2) java可重入鎖-ReentrantLock實現細節 (公平鎖)
ReentrantLock支持兩種獲取鎖的方式,一種是公平模型,一種是非公平模型。在繼續之前,咱們先把故事元素轉換爲程序元素。
咱們先來說說公平鎖模型:
初始化時, state=0,表示無人搶佔了打水權。這時候,村民A來打水(A線程請求鎖),佔了打水權,把state+1,如下所示:
線程A取得了鎖,把 state原子性+1,這時候state被改爲1,A線程繼續執行其他任務,然後來了村民B也想打水(線程B請求鎖),線程B無法獲取鎖,生成節點進行排隊,如下圖所示:
初始化的時候,會生成一個空的頭節點,然後纔是B線程節點,這時候,如果線程A又請求鎖,是否需要排隊?答案當然是否定的,否則就直接死鎖了。當A再次請求鎖,就相當於是打水期間,同一家人也來打水了,是有特權的,這時候的狀態如下圖所示:
可重入鎖就是一個線程在獲取了鎖之後,再次去獲取了同一個鎖,這時候僅僅是把狀態值進行累加。如果線程A釋放了一次鎖僅僅是把狀態值減了,只有線程A把此鎖全部釋放了,狀態值減到0了,其他線程纔有機會獲取鎖。當A把鎖完全釋放後,state恢復爲0,然後會通知隊列喚醒B線程節點,使B可以再次競爭鎖。當然,如果B線程後面還有C線程,C線程繼續休眠,除非B執行完了,通知了C線程。注意,當一個線程節點被喚醒然後取得了鎖,對應節點會從隊列中刪除。
3) java可重入鎖-ReentrantLock實現細節 (非公平鎖)
當線程A執行完之後,要喚醒線程B是需要時間的,而且線程B醒來後還要再次競爭鎖,所以如果在切換過程當中,來了一個線程C,那麼線程C是有可能獲取到鎖的,如果C獲取到了鎖,B就只能繼續乖乖休眠了。
4) 爲什麼使用可重入鎖?
ReentrantLock 是一個可重入的互斥(/獨佔)鎖,又稱爲“獨佔鎖”。
ReentrantLock通過自定義隊列同步器(AQS-AbstractQueuedSychronized,是實現鎖的關鍵)來實現鎖的獲取與釋放。
其可以完全替代 synchronized 關鍵字。JDK 5.0 早期版本,其性能遠好於 synchronized,但 JDK 6.0 開始,JDK 對 synchronized 做了大量的優化,使得兩者差距並不大。
“獨佔”,就是在同一時刻只能有一個線程獲取到鎖,而其它獲取鎖的線程只能處於同步隊列中等待,只有獲取鎖的線程釋放了鎖,後繼的線程才能夠獲取鎖。
“可重入”,就是支持重進入的鎖,它表示該鎖能夠支持一個線程對資源的重複加鎖。該鎖還支持獲取鎖時的公平和非公平性選擇。“公平”是指“不同的線程獲取鎖的機制是公平的”,而“不公平”是指“不同的線程獲取鎖的機制是非公平的”。
6.synchronized鎖
synchronized是通過互斥來保證併發的正確性的問題,synchronized經過編譯後,會在同步塊前後形成**
monitorenter
和monitorexit
**這兩個字節碼,其中monitorenter
指令指向同步代碼塊的開始位置,monitorexit
指令則指明同步代碼塊的結束位置,當執行monitorenter
指令的時,首先嚐試獲取對象的鎖,如果當前對象沒有被鎖定,或者當前對象已經擁有對象的鎖,那麼就把鎖的計數器加1,相應的在執行monitorexit
的時候會將鎖的計數器減1,當計數器爲0的時候,鎖就被釋放,如果獲取鎖的對象失敗,則當前線程就要阻塞等待,直到對象鎖被另外一個線程鎖釋放爲止正如上面所說 synchronized也是
可重入鎖
7.synchronized與Lock的區別
兩者區別:
1.首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類;
2.synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖;
3.synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖;
4.用synchronized關鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結束了;
5.synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可)
6.Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。
五、Spring Cloud 相關
1.springcloud包含的組件
- 服務發現——Netflix Eureka
- 服務調用——Netflix Feign
- 負載均衡—— Netflix Ribbon
- 熔斷器——Netflix Hystrix
- 服務網關——Spring Cloud Gateway
- 分佈式配置——Spring Cloud Config
- 消息總線 —— Spring Cloud Bus
2.Eureka
Eureka Client:負責將這個服務的信息註冊到Eureka Server中
Eureka Server:註冊中心,裏面有一個註冊表,保存了各個服務所在的機器和端口號
3.Feign
是一個http請求調用的輕量級框架,可以以Java接口註解的方式調用Http請求,而不用像Java中通過封裝HTTP請求報文的方式直接調用。Feign通過處理註解,將請求模板化,當實際調用的時候,傳入參數,根據參數再應用到請求上,進而轉化成真正的請求,這種請求相對而言比較直觀。
1.feign採用的是基於接口的註解
2.feign整合了ribbon,具有負載均衡的能力
3.整合了Hystrix,具有熔斷的能力
4.Netflix Ribbon
Spring Cloud Ribbon是一個基於HTTP和TCP的客戶端負載均衡工具,它基於Netflix Ribbon實現
5.Netflix Hystrix
服務的熔斷和降級
6.Spring Cloud Config
爲分佈式系統外部化配置提供了支持
7.Spring Cloud Bus
Spring cloud bus通過輕量消息代理連接各個分佈的節點。這會用在廣播狀態的變化(例如配置變化)或者其他的消息指令。Spring bus的一個核心思想是通過分佈式的啓動器對spring boot應用進行擴展,也可以用來建立一個多個應用之間的通信頻道。目前唯一實現的方式是用AMQP消息代理作爲通道,同樣特性的設置(有些取決於通道的設置)在更多通道的文檔中。
大家可以將它理解爲管理和傳播所有分佈式項目中的消息既可,其實本質是利用了MQ的廣播機制在分佈式的系統中傳播消息,目前常用的有Kafka和RabbitMQ。利用bus的機制可以做很多的事情,其中配置中心客戶端刷新就是典型的應用場景之一!
8.Spring Cloud Gateway
Spring Cloud Gateway是Spring Cloud官方推出的第二代網關框架,取代Zuul網關。網關作爲流量的,在微服務系統中有着非常作用,網關常見的功能有路由轉發、權限校驗、限流控制等作用。
使用了一個RouteLocatorBuilder的bean去創建路由,除了創建路由RouteLocatorBuilder可以讓你添加各種predicates和filters,predicates斷言的意思,顧名思義就是根據具體的請求的規則,由具體的route去處理,filters是各種過濾器,用來對請求做各種判斷和修改。
8.微服務的優點缺點?
優點:
1.每個服務直接足夠內聚,代碼容易理解
2.開發效率高,一個服務只做一件事,適合小團隊開發
3.松耦合,有功能意義的服務。
4.可以用不同語言開發,面向接口編程。
5.易於第三方集成
6.微服務只是業務邏輯的代碼,不會和HTML,CSS或其他界面結合.
7.可以靈活搭配,連接公共庫/連接獨立庫缺點:
1.分佈式系統的責任性
2.多服務運維難度加大。
3.系統部署依賴,服務間通信成本,數據一致性,系統集成測試,性能監控。
9.spring cloud 和dubbo區別?
1.服務調用方式 dubbo是RPC springcloud Rest Api
2.註冊中心,dubbo 是zookeeper springcloud是eureka,也可以是zookeeper
3.服務網關,dubbo本身沒有實現,只能通過其他第三方技術整合,springcloud有Zuul路由網關,作爲路由服務器,進行消費者的請求分發,springcloud支持斷路器,與git完美集成配置文件支持版本控制,事物總線實現配置文件的更新與服務自動裝配等等一系列的微服務架構要素。
10.REST 和RPC對比
1.RPC主要的缺陷是服務提供方和調用方式之間的依賴太強,需要對每一個微服務進行接口的定義,並通過持續繼承發佈,嚴格版本控制纔不會出現衝突。
2.REST是輕量級的接口,服務的提供和調用不存在代碼之間的耦合,只需要一個約定進行規範。
11.負載均衡的意義是什麼?
在計算中,負載均衡可以改善跨計算機,計算機集羣,網絡鏈接,中央處理單元或磁盤驅動器等多種計算資源的工作負載分佈。負載均衡旨在優化資源使用,最大吞吐量,最小響應時間並避免任何單一資源的過載。使用多個組件進行負載均衡而不是單個組件可能會通過冗餘來提高可靠性和可用性。負載平衡通常涉及專用軟件或硬件,例如多層交換機或域名系統服務進程。
12.微服務之間是如何獨立通訊的?
1.遠程調用,比如feign調用,直接通過遠程過程調用來訪問別的service。
2.消息中間件
13.Eureka和Zookeeper區別
1.Eureka取CAP的AP,注重可用性,Zookeeper取CAP的CP注重
一致性。
2.Zookeeper在選舉期間註冊服務癱瘓,雖然服務最終會恢復,但選舉期間不可用。
3.eureka的自我保護機制,會導致一個結果就是不會再從註冊列表移除因長時間沒收到心跳而過期的服務。依然能接受新服務的註冊和查詢請求,但不會被同步到其他節點。不會服務癱瘓。
4.Zookeeper有Leader和Follower角色,Eureka各個節點平等。
5.Zookeeper採用過半數存活原則,Eureka採用自我保護機制解決分區問題。
6.eureka本質是一個工程,Zookeeper只是一個進程。
14.eureka自我保護機制是什麼?
1.當Eureka Server 節點在短時間內丟失了過多實例的連接時(比如網絡故障或頻繁啓動關閉客戶端)節點會進入自我保護模式,保護註冊信息,不再刪除註冊數據,故障恢復時,自動退出自我保護模式。
15.什麼是服務熔斷?什麼是服務降級?
服務直接的調用,比如在高併發情況下出現進程阻塞,導致當前線程不可用,慢慢的全部線程阻塞,導致服務器雪崩。
服務熔斷:相當於保險絲,出現某個異常,直接熔斷整個服務,而不是一直等到服務超時。通過維護一個自己的線程池,當線程到達閾值的時候就啓動服務降級,如果其他請求繼續訪問就直接返回fallback的默認值。
六、發揮題
1.設計一個RPC框架模型,你應該注意的是什麼!他至少應該有哪些組件?
2.設計一個任務隊列,java原生語法編寫;注意事項是什麼?
PS:其實後面還有,不過沒有給出具體的題目讓隨意發揮,類似這樣,後續作者會再補充一些,敬請期待!
才疏學淺,如果文章中理解有誤,歡迎大佬們私聊指正!歡迎關注作者的公衆號,一起進步,一起學習!