2020年如何面試大廠?阿里架構(gòu)師良心分享美團驼仪、滴滴Java崗面試真題

題記:本文會盡量模擬面試現(xiàn)場對話情景掸犬, 用口語而非書面語 ,采用問答形式來展現(xiàn)绪爸。另外每一個問題都附上“延伸”湾碎,這部分內(nèi)容是幫助小伙伴們更深的理解一些底層細(xì)節(jié)的補充,在面試中可能很少直接涉及奠货,權(quán)當(dāng)是提高自身水平的知識儲備吧介褥。

一、Java基礎(chǔ)

1. 問:List 和 Set 都有什么區(qū)別?

分析:這種問題面試官一般想考察的都是你對這兩種數(shù)據(jù)結(jié)構(gòu)的了解柔滔,已經(jīng)使用時候的選擇依據(jù)溢陪,所以可以從數(shù)據(jù)結(jié)構(gòu)和一些使用案例入手分別做個介紹

答:List,列表睛廊,元素可重復(fù)功氨。常用的實現(xiàn)ArrayList和LinkedList浅乔,前者是數(shù)組方式來實現(xiàn)窒篱,后者是通過鏈表來實現(xiàn)穷绵,在使用選擇的時候,一般考慮的是基本數(shù)據(jù)結(jié)構(gòu)的特性嘶朱,比如蛾坯,數(shù)組讀取效率較高,鏈表插入時效率較高疏遏。

Set脉课,集合,特點就是存儲的元素不可重復(fù)改览。常用是實現(xiàn)下翎,是HashSet和TreeSet,分開來談:

HashSet宝当,底層實現(xiàn)是HashMap视事,存儲時,把值存在key庆揩,而value統(tǒng)一存儲一個object對象俐东。排重的時候,是先通過對象的hashcode來判斷订晌,如果不相等虏辫,直接存儲。如果相等锈拨,會再對key做equals判斷砌庄,如果依然相等,不存儲奕枢,如果不相等娄昆,則存入,我們知道缝彬,HashMap是數(shù)組+鏈表的基本結(jié)構(gòu)萌焰,同樣的,在HashSet中谷浅,也是通過同樣的策略扒俯,存儲在相同的數(shù)組位置下的鏈表中奶卓。

TreeSet,存入自定義對象時撼玄,對象需要實現(xiàn)Comparable接口夺姑,重寫排序規(guī)則。使用場景一般是需要保證存儲數(shù)據(jù)的有序和唯一性互纯。底層數(shù)據(jù)結(jié)構(gòu)是自平衡的二叉排序樹(紅黑樹)

延伸

  1. 在說到HashMap時瑟幕,可能會直接引到HashMap相關(guān)話題,我發(fā)現(xiàn)這個問題面試官非常喜歡問留潦,可能是因為HashMap可聊的較多,也能很好的考驗下應(yīng)聘者對底層實現(xiàn)細(xì)節(jié)辣往、源碼閱讀兔院、刨根問底的態(tài)度。
  2. 涉及到二叉樹了站削,小端就遇到過一次問紅黑樹特性的坊萝,因為之前準(zhǔn)備過,胸有成竹的啪啪啪正要一一道來呢许起,結(jié)果剛說到第二個特性十偶,面試官就問:紅黑樹和普通的平衡二叉樹有什么區(qū)別?當(dāng)時一臉懵逼樣...回來后趕緊補足园细,核心區(qū)別就是:紅黑樹也是二叉查找樹的一種惦积,二叉樹需要通過自旋、或其他方式(比如紅黑樹還能通過變色)來保證平衡(否則就成了鏈表結(jié)構(gòu)了猛频,沒有時間復(fù)雜度上的優(yōu)勢了)狮崩,紅黑樹限定的條件相對來說比較寬松,也就是說在平衡的過程中鹿寻,消耗相對較小睦柴。
  3. 由于HashSet無序,為了實現(xiàn)有序的目的毡熏,又不想用其他數(shù)據(jù)結(jié)構(gòu)坦敌,可以用LinkedHashSet。簡要說明痢法,同HashSet和HashMap關(guān)系一樣狱窘,也是使用了一個LinkedHashMap,LinkedHashMap和普通的HashMap的區(qū)別就是疯暑,在原有數(shù)據(jù)結(jié)構(gòu)之上训柴,采用雙向鏈表的形式將所有entry(注意,是前面講過的數(shù)組+鏈表中的各個鏈表里的元素妇拯,做了連接)連起來幻馁,順序就是entry的插入順序洗鸵,這樣可以保證元素的迭代順序和插入順序相同(有序性)

2. HashMap 是線程安全的嗎,為什么不是線程安全的?

分析:這種問題仗嗦,既然面試官問起膘滨,肯定是在這方面有的聊的。這個問題稀拐,在多數(shù)情況下火邓,能清晰、全面的描述出問題來龍去脈即可德撬,很少有面試官真的拿一段源碼來考铲咨。當(dāng)然,作為應(yīng)聘者蜓洪,如果能理解的很透徹纤勒,再用簡單的圖邊寫邊講,作為補充隆檀,還是非常出彩的摇天。

答:嗯,不是線程安全的恐仑。主要從兩個方面來說:

  • 在插入新數(shù)據(jù)的時候泉坐,多線程hash后的結(jié)果相同,插入位置也就會定位到數(shù)組的相同下標(biāo)下的同一個鏈表中裳仆。在插入時腕让,假如第二個線程在第一個線程插入的瞬間也插入,就可能會導(dǎo)致鉴逞,覆蓋前面插入的值记某。
  • 第二個非線程安全的影響是在擴容的時候,擴容會把所有值重新hash构捡,插入到新的擴容后的“數(shù)組+鏈表”結(jié)構(gòu)中液南。可能會導(dǎo)致環(huán)狀鏈表情況出現(xiàn)勾徽,這樣在讀取節(jié)點的next節(jié)點時滑凉,永不為空,也就是著名的擴容時CPU使用率100%情況喘帚。

延伸

要做到心中有底畅姊,還是需要知其然知其所以然才行,所以吹由,擼下源碼好好想想若未,才能做到臨陣不亂。

  1. 建議好好看看源碼倾鲫,源碼量不大粗合,結(jié)構(gòu)也比較清晰萍嬉。
  2. 針對環(huán)狀鏈表的情況,我是看了別人寫的圖文并茂版的文章弄明白的隙疚,這里放上鏈接壤追,供參考:HashMap高并發(fā)問題

3. 問:那你是用什么數(shù)據(jù)結(jié)構(gòu)來作為替代,滿足線程安全的場景要求呢供屉?

分析:這里一般面試官想考察的是對ConcurrentHashMap的了解行冰,但是也有個別情況,會涉及到HashTable伶丐,小端就吃過這方面的虧悼做,明明可以一筆帶過的小知識點,卻由于準(zhǔn)備不充分撵割,而沒能答完整贿堰。

答:在Java里,提供了以下數(shù)據(jù)結(jié)構(gòu)啡彬,可以解決線程安全問題:

  • HashTable,實現(xiàn)原理是通過Synchronize同步鎖來把讀寫方法都加鎖的方式故硅。雖然線程安全了庶灿,但是效率低下,只要有讀寫吃衅,就不能做其他操作往踢。
  • SynchronizedMap(涉及較少,了解即可)徘层,有條件的同步峻呕,是Collections類提供的一個方法返回的HashMap的多線程版本。實現(xiàn)是把基本的方法都加了同步鎖趣效。
  • ConcurrentHashMap(重頭戲):根據(jù)jdk版本不同瘦癌,實現(xiàn)也有差別。

java8以前跷敬,是用segment(鎖住map一段實現(xiàn)讯私,默認(rèn)是16段也就是可以并發(fā)數(shù)支持到16,也可自定義西傀。讀不受影響)斤寇,數(shù)據(jù)結(jié)構(gòu)為數(shù)組(segment)+數(shù)組(entry)+鏈表,適用于讀多寫少的場景拥褂。提供原子操作putIfAbsent()(沒有則添加)娘锁。segment繼承自ReentrantLock,來作為鎖饺鹃。

java8莫秆,元素結(jié)構(gòu)為Node(實現(xiàn)Entry接口)间雀,數(shù)據(jù)結(jié)構(gòu)為數(shù)組+鏈表;直接對Node進(jìn)行加鎖馏锡,粒度更小雷蹂。當(dāng)鏈表長度大于8,轉(zhuǎn)換為紅黑樹,當(dāng)然在轉(zhuǎn)換前杯道,先看下數(shù)組長度匪煌,如果小于64,那先通過擴容來實現(xiàn)党巾;插入元素時萎庭,如果該位置為null,用CAS插入齿拂;如果不為null驳规,則加Synchronize鎖插入到鏈表;

擴展

  • 我們看到署海,數(shù)組的默認(rèn)長度都是16吗购,那么這個數(shù)字有什么深意嗎?有砸狞!做運算時效率高捻勉!屬于取巧的設(shè)計。一個是擴容的時候刀森,直接向左位操作踱启,移一位,擴張為二倍研底;二是hash取模的時候埠偿,hash值是32位,右移28位榜晦,剩高四位冠蒋,然后與這個length-1也就是15(1111)按位與操作,使數(shù)據(jù)均勻分布芽隆。
  • ConcurrentHashMap在獲取size時候是怎么計算的呢浊服?

1.7size(),先獲取segment的大小,然后判斷是否修改過胚吁,如果是牙躺,在加鎖重新獲取segment大小,然后把所有segment大小加在一起腕扶;
1.8size()的實現(xiàn)是用一個volatile 變量來做CAS修改孽拷,如果高并發(fā),還會把部分值存到一個volatile數(shù)組半抱。取值時脓恕,把這兩部分的值加在一起膜宋。mappingcount()方法和size()方法實現(xiàn)方式一樣

  • hashmap可以使用null作為key和value,存儲于table數(shù)組第一個節(jié)點炼幔;hashtable不允許key和value為null.(這個在一個小公司被面試官問倒了秋茫,汗顏)
  • 了解一些其他的數(shù)據(jù)結(jié)構(gòu):
  • ConcurrentSkipListMap 數(shù)據(jù)有序
  • ConcurrentSkipListSet 能去重
  • hashset是用hashmap實現(xiàn),內(nèi)部存的是key乃秀,所有的value都是同一個object

二肛著、ThreadLocal

1. 問:請談?wù)勀銓hreadLocal的理解。

分析:在多線程環(huán)境下跺讯,我們經(jīng)常遇到這樣的場景:維護(hù)一個全局變量枢贿。如果要保證變量值的正確性(或者說變量值修改的原子性),需用什么方式來實現(xiàn)呢刀脏?是的局荚,對修改代碼加鎖可以實現(xiàn),保證了在同一時刻只有一個線程來修改該變量值愈污。辦法當(dāng)然不止一種耀态,并發(fā)包AtomicXXX一樣能達(dá)到這個效果,原理暂雹,差不多茫陆,無非是通過鎖來實現(xiàn)并發(fā)。那么還有沒有其他思路呢擎析?有,ThreadLocal挥下,實現(xiàn)思路可謂是另辟蹊徑揍魂。

答:每個線程,都會有一個Map(ThreadLocalMap)棚瘟,用來存儲以我們定義的ThreadLocal對象為key现斋,以我們自定義的值為value的 名值對。而這個Map偎蘸,是來自于我們寫的多線程程序繼承的父線程Thread庄蹋。以此機制,保證了多線程間該變量值的隔離迷雪。

看下源碼限书,以get()方法為切入口:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
                              T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

重點是第三行,當(dāng)前線程作為參數(shù)傳入章咧,我們來看下getMap(t)做了什么倦西?

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

是的,拿到當(dāng)前線程對象的threadLocals對象赁严,我們可以通過方法返回值推斷扰柠,是一個ThreadLocalMap類型的對象粉铐。那么這個對象在哪定義的呢?繼續(xù)看源碼:

public class Thread implements Runnable {
    ......
         ThreadLocal.ThreadLocalMap threadLocals = null;
    ......
}

很明顯卤档,是在Thread類里定義蝙泼。

擴展:內(nèi)存泄漏問題。

ThreadLocal對象是弱引用劝枣。在GC時汤踏,會直接回收。這種情況下哨免,Map中的key為null茎活,value值還在,無法得到及時的釋放琢唾。目前的策略是在調(diào)用get载荔、set、remove等方法時采桃,會啟動回收這些值懒熙。但是如果一直沒調(diào)用呢?嗯普办,很容易就導(dǎo)致內(nèi)存泄漏了工扎。當(dāng)然,并不能因為此就認(rèn)為是弱引用導(dǎo)致的內(nèi)存泄露衔蹲,而應(yīng)該是肢娘,設(shè)計的這個變量存儲機制,導(dǎo)致了泄露舆驶。所以在使用的時候橱健,要及時釋放(通過以上描述,你肯定已經(jīng)想到怎么合理釋放了吧沙廉?)

三拘荡、Valotile

1. 問:請你說下對Valotile的了解,以及使用場景撬陵。

分析:多線程編程珊皿,我們要解決的問題集中在三個方面:

  • 原子性:最簡單的例子就是,i++,在多線程環(huán)境下巨税,最終的結(jié)果是不確定的蟋定,為什么?就是因為這么一個++操作垢夹,被編譯為指令 后溢吻,是多個指令來完成的。那么遇到并發(fā)的情況,就會導(dǎo)致彼此“覆蓋”的情況促王。
  • 可見性:通俗解釋就是犀盟,在A線程對一個變量做了修改,在B線程中蝇狼,能正確的讀取到修改后的結(jié)果阅畴。究其原理,是cpu不是直 接 和系統(tǒng)內(nèi)存通信迅耘,而是把變量讀取到L1贱枣,L2等內(nèi)部的緩存中,也叫作私有的數(shù)據(jù)工作棧颤专。修改也是在內(nèi)部緩存中纽哥,但是何時同步到系統(tǒng)內(nèi)存是不能確定的,有了這個時間差栖秕,在并發(fā)的時候春塌,就可能會導(dǎo)致,讀到的值簇捍,不是最新值只壳。
  • 有序性:這里只說指令重排序,虛擬機在把代碼編譯為指令后執(zhí)行暑塑,出于優(yōu)化的目的吼句,在保證結(jié)果不變的情況下,可能會調(diào)整指 令的執(zhí)行順序事格。

答:valotile惕艳,能滿足上述的可見性和有序性。但是無法保證原子性驹愚。

  • 可見性尔艇,是在修改后,強制把對變量的修改同步到系統(tǒng)內(nèi)存么鹤。而其他cpu在讀取自己的內(nèi)部緩存中的值的時候,發(fā)現(xiàn)是valotile修飾 的味廊,會把內(nèi)部緩存中的值蒸甜,置為無效,然后從系統(tǒng)內(nèi)存讀取余佛。
  • 有序性柠新,是通過內(nèi)存屏障來實現(xiàn)的。所謂的內(nèi)存屏障辉巡,可以理解為恨憎,在某些指令中,插入屏障指令,用以確保憔恳,在向屏障指令后面 繼續(xù)執(zhí)行的時候瓤荔,其前面的所有指令已經(jīng)執(zhí)行完畢。

擴展:在寫單例模式時钥组,我們通常會采用雙層判斷的方式输硝,在最內(nèi)層:

instance = new Singleton()

其實這也有一個隱含的問題:這句賦值語句,其實是分三步來操作的:

  • 為instance分配內(nèi)存
  • 調(diào)用Singleto構(gòu)造函數(shù)來初始化變量
  • instance指向上一步初始化的對象

在jvm做了指令重排序優(yōu)化后程梦,上述步驟b和c不能保證点把,可能出現(xiàn),c先執(zhí)行屿附,但是對象卻沒初始化郎逃,這時候其他線程判斷的時候,發(fā)現(xiàn)是非null挺份,但是使用的時候褒翰,卻沒有具體實例,導(dǎo)致報錯压恒。所以影暴,我們可以用valotile來修飾instance,避免該問題探赫。

四型宙、多線程——Synchronized

1. 問:你平時涉及到多線程編程多不多?談?wù)勀銓ynchronized鎖的理解

分析:多從實現(xiàn)原理伦吠,工作機制來描述

答:在多線程編程中妆兑,為了達(dá)到線程安全的目的,我們往往通過加鎖的方式來實現(xiàn)毛仪。而Synchronized正是java提供給我們的非常重要的鎖之一搁嗓。它屬于jvm級別加鎖,底層實現(xiàn)是:在編譯過程中箱靴,在指令級別加入一些標(biāo)識來實現(xiàn)的腺逛。例如,同步代碼塊衡怀,會在同步代碼塊首尾加入monitorenter和monitorexit字節(jié)碼指令棍矛,這兩個指令都需要一個reference類型的參數(shù),指明要加鎖和解鎖的對象抛杨,同步方法則是通過在修飾符上加acc_synchronized標(biāo)識實現(xiàn)够委。在執(zhí)行到這些指令(標(biāo)識)時,本質(zhì)都是獲取怖现、占有茁帽、釋放monitor鎖對象實現(xiàn)互斥,也就是說,同一時刻潘拨,只能有一個線程能成功的獲取到這個鎖對象吊输。我們看一段加了synchronized關(guān)鍵字的代碼編譯后的字節(jié)碼。編譯前:

public class test {
  public test() {
  }
  public static void main(String[] args) {
    synchronized(new Object()){
        int i = 0;
    }
  }
}

編譯后:

public class test extends java.lang.Object{
public test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."":()V
   4:   nop
   5:   return

public static void main(java.lang.String[]);
  Code:
   0:   new     #2; //class Object
   3:   dup
   4:   invokespecial   #1; //Method java/lang/Object."":()V
   7:   dup
   8:   astore_1
   9:   monitorenter // Enter the monitor associated with object 
   10:  iconst_0
   11:  istore_2
   12:  nop
   13:  aload_1
   14:  monitorexit // Exit the monitor associated with object 
   15:  goto    23
   18:  astore_3
   19:  aload_1
   20:  monitorexit // Be sure to exit monitor... 
   21:  aload_3
   22:  athrow
   23:  nop
   24:  return
  Exception table:
   from   to  target type
   15    18   any
   21    18   any

}

重點關(guān)注14行战秋,和20行璧亚。

在使用Synchronized時,用到的方法是wait和notify(或notifyAll)脂信,他們的原理是癣蟋,調(diào)用wait方法后,線程讓出cpu資源狰闪,釋放鎖疯搅,進(jìn)入waiting狀態(tài),進(jìn)入等待隊列【第一個隊列】埋泵。當(dāng)有其他線程調(diào)用了notify或者notifyAll喚醒時幔欧,會將等待隊列里的線程對象,移入阻塞隊列【第二個隊列】丽声,狀態(tài)是blocked礁蔗,等待鎖被釋放后(這個釋放時機,由虛擬機來決定雁社,人為無法干預(yù))浴井,開始競爭鎖。

Synchronized無法中斷正在阻塞隊列或者等待隊列的線程霉撵。

擴展:Synchronized提供了以下幾種類型的鎖:偏向鎖磺浙、輕量級鎖、重量級鎖徒坡。在大部分情況下撕氧,并不存在多線程競爭,更常見的是一個線程多次獲取同一個鎖喇完。那么很多的消耗伦泥,其實是在鎖的獲取與釋放上。Synchronized一直在被優(yōu)化锦溪,可以說Synchronized雖然推出的較早奄喂,但是效率并不比后來推出的Lock差。

偏向鎖:在jdk1.6中引入海洼,目的是消除在無競爭情況下的同步原語(翻譯成人話就是,即使加了synchronized關(guān)鍵字富腊,但是在沒有競爭的時候坏逢,沒必要去做獲取-持有-釋放鎖對象的操作,提高程序運行性能)。怎么做呢是整?當(dāng)鎖對象第一次被線程A獲取時肖揣,虛擬機會把對象頭中的標(biāo)志位設(shè)置為01,也就是代表偏向模式浮入。同時把代表這個線程A的ID龙优,通過CAS方式,更新到鎖對象頭的MarkWord中事秀。相同的線程下次再次申請鎖的時候彤断,只需要簡單的比較線程ID即可。以上操作成功易迹,則成功進(jìn)入到同步代碼塊宰衙。如果此時有其他線程B來競爭該鎖,分兩種情況做不同的處理:

  • 如果線程A已執(zhí)行完(并不會主動的修改鎖對象的狀態(tài))睹欲,會直接撤銷鎖對象的狀態(tài)為未鎖定供炼,標(biāo)志位為00;
  • 如果線程A還在持有該鎖窘疮,則鎖升級為輕量級鎖袋哼。

輕量級鎖:也是JDK1.6中引入的,輕量級闸衫,是相對于使用互斥量的重量級鎖來說的涛贯。線程發(fā)生競爭鎖的時候,不會直接進(jìn)入阻塞狀態(tài)楚堤,而是先嘗試做CAS修改操作疫蔓,進(jìn)入自旋,這個過程避免了線程狀態(tài)切換的開銷身冬,不過要消耗cpu資源衅胀。詳細(xì)過程是:

  1. 線程嘗試進(jìn)入同步代碼塊,如果鎖對象未被鎖定酥筝,在當(dāng)前線程對應(yīng)的棧幀中滚躯,建立鎖記錄的空間,用于存儲鎖對象Mark Word的拷貝嘿歌。
  2. 然后JVM用CAS方式嘗試將鎖對象的MarkWord內(nèi)容替換為指向前述“鎖記錄”的指針掸掏。如果成功,當(dāng)前線程則持有了鎖宙帝,處于輕量級鎖定狀態(tài)丧凤;如果失敗,會首先檢查當(dāng)前MarkWord是否已經(jīng)指向當(dāng)前線程棧幀的“鎖記錄”步脓,如果是愿待,就說明當(dāng)前線程已經(jīng)擁有了這個鎖浩螺,直接重入即可。否則就表明是其他線程持有鎖仍侥,那么進(jìn)入自旋(其實就是重試CAS修改操作)要出。
  3. 釋放鎖時,是使用CAS來講MarkWord和“鎖記錄”里的內(nèi)容互換农渊。如果成功患蹂,成功釋放;如果事變砸紊,表明當(dāng)前鎖存在競爭(被其他線程修改了MarkWord里的數(shù)據(jù))传于,此時,鎖會升級為重量級鎖批糟。

重量級鎖:也就是我們使用的互斥量方式實現(xiàn)的鎖格了,當(dāng)存在多線程競爭時,只要沒拿到鎖徽鼎,就會進(jìn)入阻塞狀態(tài)盛末,主要消耗是在阻塞-喚起-阻塞-喚起的線程狀態(tài)切換上。

上面介紹的三種類型的鎖否淤,是JVM來負(fù)責(zé)管理使用哪種類型鎖悄但,以及鎖的升級(注意,沒有降級)石抡。

五檐嚣、多線程——Lock

1. 問:你平時涉及到多線程編程多不多?談?wù)勀銓ock鎖的理解

分析:最好對比著synchronized來講

答:

在多線程編程中啰扛,為了達(dá)到線程安全的目的嚎京,我們往往通過加鎖的方式來實現(xiàn)。Lock鎖是java代碼級別來實現(xiàn)的隐解,相對于synchronizedd在功能性上鞍帝,有所加強,主要是煞茫,公平鎖帕涌,輪詢鎖,定時鎖续徽,可中斷鎖等蚓曼,還增加了多路通知機制(Condition),可以用一個鎖來管理多個同步塊钦扭。另外在使用的時候纫版,必須手動的釋放鎖。

詳細(xì)分析:

  • Lock鎖的實現(xiàn)客情,主要是借助于隊列同步器(我們常常見到的AQS)來實現(xiàn)其弊。它包括一個int變量來表示狀態(tài)会涎;一個FIFO隊列,來存儲獲取資源的排隊線程瑞凑。
  • 當(dāng)一個線程申請資源時,就是是獲取當(dāng)前的同步狀態(tài)概页,并判斷是否可符合預(yù)期籽御,如果是,則通過CAS操作惰匙,來修改上述Int變量標(biāo)識的同步狀態(tài)技掏。如果否,則線程進(jìn)入隊列排隊(這是在一般情況项鬼,在使用tyrLock時哑梳,是直接返回獲取鎖失敗)绘盟。

鎖有獨占鎖和共享鎖鸠真。獨占鎖就是在同一時刻,只允許同一個線程持有該鎖龄毡;共享鎖實現(xiàn)的時候和獨占鎖稍有不同吠卷,不是簡單的修改同步狀態(tài)(比如1和0),而是獲取這個值沦零,當(dāng)值大于0時祭隔,即標(biāo)識獲取共享鎖成功(隱含意思是每個線程獲取鎖成功后,這個值減1)路操。這里附上獨占鎖的實現(xiàn)源碼(源碼片段來自《java并發(fā)編程的藝術(shù)》疾渴,并加上自己的注釋):

public class Mutex implements Lock {  
  
    // 靜態(tài)內(nèi)部類,自定義同步器  
    private static class Sync extends AbstractQueuedSynchronizer{  
        // 該方法用于判斷當(dāng)前鎖是否在獨占模式下被占用狀態(tài)  
        protected boolean isHeldExclusively(){  
            return getState() == 1;  
        }  
  
        // 獲取鎖!!! 
        public boolean tryAcquire(int acquires){ 
          //典型的CAS原子操作屯仗,如果初始狀態(tài)為0搞坝,可以獲得鎖
            if (compareAndSetState(0, 1)){  
                setExclusiveOwnerThread(Thread.currentThread());  
                return true;  
            }  
            return false;  
        }  
  
        //釋放鎖,將當(dāng)前狀態(tài)設(shè)置為0  
        protected boolean tryRelease(int releases){  
            if (getState() == 0){  
                throw new IllegalMonitorStateException();  
            }  
            setExclusiveOwnerThread(null);  
            setState(0);  
            return true;  
        }  
  
        // 返回一個Condition祭钉,每個condition都包含了一個condition隊列 瞄沙,這個后續(xù)再說 
        Condition newCondition(){  
            return new ConditionObject();  
        }  
    }
  • Lock鎖中,支持可中斷的鎖慌核,實現(xiàn)原理是距境,隊列中的等待線程,可以響應(yīng)其他線程發(fā)起的中斷信號垮卓,拋出InterruptdException異常垫桂。
  • 關(guān)于同步隊列,需要了解粟按,獲取同步狀態(tài)失敗的線程诬滩,被包裝為Node節(jié)點后霹粥,加入隊列尾,這個操作是CAS操作疼鸟,以保證線程安全后控,失敗就死循環(huán)重試;而隊列首節(jié)點空镜,則是當(dāng)前持有鎖的線程浩淘。該節(jié)點一旦釋放鎖缓窜,會喚醒后繼節(jié)點币旧。
  • 關(guān)于喚醒灾炭,是這樣的檬嘀,每個在同步隊列中的阻塞線程峭状,都處于自旋的狀態(tài)谅河,不斷的嘗試獲取鎖行施。這樣延刘,當(dāng)首節(jié)點釋放鎖喚醒后繼線程后镣隶,被喚醒的線程极谊,還需要判斷是否前繼線程是首線程,是則獲取同步狀態(tài)(鎖)成功矾缓。

擴展:Condition怀酷,多路通知機制

  • 在Synchronized鎖中,提供了wait嗜闻、notify蜕依、notifyAll等方法,實現(xiàn)了等待/通知模式琉雳。那么在lock中样眠,由Condition配合,也實現(xiàn)了類似的模式翠肘。
  • 其實現(xiàn)實質(zhì)是檐束,一個Condition包含一個等待隊列,定義多個Condition束倍,那就有多個等待隊列被丧,和上文提到的同步隊列配合使用。同步隊列-等待隊列模型請參
  • 在上述模型中绪妹,調(diào)用await方法甥桂,相當(dāng)于把同步隊列首節(jié)點(持有鎖的線程),移動到等待隊列邮旷。調(diào)用signal方法喚醒阻塞的線程黄选,則是將對應(yīng)Condition等待隊列里的首節(jié)點(等待時間最長),移入同步隊列婶肩。
  • 還有一點需要補充办陷,就是線程的喚醒貌夕,調(diào)用signal可以正常喚醒;在其他線程中終止線程民镜,也一樣會喚醒啡专,只不過喚醒后,只是拋出InterruptException異常制圈。

# 鏈接 Java程序員福利"常用資料分享"

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末植旧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子离唐,更是在濱河造成了極大的恐慌,老刑警劉巖问窃,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亥鬓,死亡現(xiàn)場離奇詭異,居然都是意外死亡域庇,警方通過查閱死者的電腦和手機嵌戈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來听皿,“玉大人熟呛,你說我怎么就攤上這事∥疽蹋” “怎么了庵朝?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長又厉。 經(jīng)常有香客問我九府,道長,這世上最難降的妖魔是什么覆致? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任侄旬,我火速辦了婚禮,結(jié)果婚禮上煌妈,老公的妹妹穿的比我還像新娘儡羔。我一直安慰自己,他們只是感情好璧诵,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布汰蜘。 她就那樣靜靜地躺著,像睡著了一般腮猖。 火紅的嫁衣襯著肌膚如雪鉴扫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天澈缺,我揣著相機與錄音坪创,去河邊找鬼炕婶。 笑死,一個胖子當(dāng)著我的面吹牛莱预,可吹牛的內(nèi)容都是我干的柠掂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼依沮,長吁一口氣:“原來是場噩夢啊……” “哼涯贞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起危喉,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤宋渔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后辜限,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體皇拣,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年薄嫡,在試婚紗的時候發(fā)現(xiàn)自己被綠了氧急。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡毫深,死狀恐怖吩坝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哑蔫,我是刑警寧澤钉寝,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站闸迷,受9級特大地震影響瘩蚪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜稿黍,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一疹瘦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧巡球,春花似錦言沐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至矿筝,卻和暖如春起便,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工榆综, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留妙痹,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓鼻疮,卻偏偏與公主長得像怯伊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子判沟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 本系列出于AWeiLoveAndroid的分享耿芹,在此感謝,再結(jié)合自身經(jīng)驗查漏補缺挪哄,完善答案吧秕。以成系統(tǒng)。 Java基...
    濟公大將閱讀 1,528評論 1 6
  • 不足的地方請大家多多指正迹炼,如有其它沒有想到的常問面試題請大家多多評論寇甸,一起成長,感謝!~ String可以被繼承嗎...
    啟示錄是真的閱讀 2,935評論 3 3
  • Java SE 基礎(chǔ): 封裝疗涉、繼承、多態(tài) 封裝: 概念:就是把對象的屬性和操作(或服務(wù))結(jié)合為一個獨立的整體吟秩,并盡...
    Jayden_Cao閱讀 2,108評論 0 8
  • layout: posttitle: 《Java并發(fā)編程的藝術(shù)》筆記categories: Javaexcerpt...
    xiaogmail閱讀 5,815評論 1 19
  • 相關(guān)概念 面向?qū)ο蟮娜齻€特征 封裝,繼承,多態(tài).這個應(yīng)該是人人皆知.有時候也會加上抽象. 多態(tài)的好處 允許不同類對...
    東經(jīng)315度閱讀 1,940評論 0 8