引言 昨日接了一個阿裡外包的電話面試,問了一些技術問題感覺到自己是真的菜,接觸Java開發已經也有一段時間,技術方面說來慚愧,一直以來只是局限於框架工具的用法,也沒有進行瞭解其實現的原理,更重要的是一直沒有歸納和總結,這次把這些問題記錄下來,相關的知識點也找了一些資料學習下。 問題 1. Count ...
引言
昨日接了一個阿裡外包的電話面試,問了一些技術問題感覺到自己是真的菜,接觸Java開發已經也有一段時間,技術方面說來慚愧,一直以來只是局限於框架工具的用法,也沒有進行瞭解其實現的原理,更重要的是一直沒有歸納和總結,這次把這些問題記錄下來,相關的知識點也找了一些資料學習下。
問題
1. CountDownLanch的工作原理
實現原理:計數器的值由構造函數傳入,並用它初始化AQS的state值。當線程調用await方法時會檢查state的值是否為0,如果是就直接返回(即不會阻塞);如果不是,將表示該節點的線程入列,然後將自身阻塞。當其它線程調用countDown方法會將計數器減1,然後判斷計數器的值是否為0,當它為0時,會喚醒隊列中的第一個節點,由於CountDownLatch使用了AQS的共用模式,所以第一個節點被喚醒後又會喚醒第二個節點,以此類推,使得所有因await方法阻塞的線程都能被喚醒而繼續執行。
引用別人的博客里的一段話,詳細請點擊:Java併發包中CountDownLatch的工作原理、使用示例
題外話:
什麼是 AQS(抽象的隊列同步器)
AbstractQueuedSynchronizer類如其名,抽象的隊列式的同步器,AQS定義了一套多線程訪問 共用資源的同步器框架,許多同步類實現都依賴於它,如常用的 ReentrantLock/Semaphore/CountDownLatch。
它維護了一個 volatile int state(代表共用資源)和一個 FIFO 線程等待隊列(多線程爭用資源被 阻塞時會進入此隊列)。這裡 volatile 是核心關鍵詞,具體 volatile 的語義,在此不述。state 的 訪問方式有三種: getState() setState() compareAndSetState()
AQS只是一個框架,具體資源的獲取/釋放方式交由自定義同步器去實現,AQS這裡只定義了一個 介面,具體資源的獲取交由自定義同步器去實現了(通過state的get/set/CAS)之所以沒有定義成 abstract,是因為獨占模式下只用實現 tryAcquire-tryRelease,而共用模式下只用實現 tryAcquireShared-tryReleaseShared。如果都定義成abstract,那麼每個模式也要去實現另一模 式下的介面。不同的自定義同步器爭用共用資源的方式也不同。自定義同步器在實現時只需要實 現共用資源 state 的獲取與釋放方式即可,至於具體線程等待隊列的維護(如獲取資源失敗入隊/ 喚醒出隊等),AQS已經在頂層實現好了。自定義同步器實現時主要實現以下幾種方法:
1. isHeldExclusively():該線程是否正在獨占資源。只有用到condition才需要去實現它。2. tryAcquire(int):獨占方式。嘗試獲取資源,成功則返回true,失敗則返回false。
3. tryRelease(int):獨占方式。嘗試釋放資源,成功則返回true,失敗則返回false。
4. tryAcquireShared(int):共用方式。嘗試獲取資源。負數表示失敗;0 表示成功,但沒有剩餘 可用資源;正數表示成功,且有剩餘資源。
5. tryReleaseShared(int):共用方式。嘗試釋放資源,如果釋放後允許喚醒後續等待結點返回 true,否則返回false。
AQS定義兩種資源共用方式
1.Exclusive獨占資源 -ReentrantLock Exclusive(獨占,只有一個線程能執行,如ReentrantLock)
2.Share共用資源 -Semaphore/CountDownLatch Share(共用,多個線程可同時執行,如Semaphore/CountDownLatch)。
3.實現獨占和共用兩種 方式,一般來說,自定義同步器要麼是獨占方法,要麼是共用方式,他們也只需實現 tryAcquiretryRelease、tryAcquireShared-tryReleaseShared 中的一種即可。但 AQS 也支持自定義同步器 同時實現獨占和共用兩種方式,如ReentrantReadWriteLock
同步器的實現是ABS核心,以ReentrantLock為例,state初始化為0,表示未鎖定狀態。A線程 lock()時,會調用 tryAcquire()獨占該鎖並將 state+1。此後,其他線程再 tryAcquire()時就會失 敗,直到A線程unlock()到state=0(即釋放鎖)為止,其它線程才有機會獲取該鎖。當然,釋放 鎖之前,A 線程自己是可以重覆獲取此鎖的(state 會累加),這就是可重入的概念。但要註意, 獲取多少次就要釋放多麼次,這樣才能保證state是能回到零態的。
以CountDownLatch以例,任務分為N個子線程去執行,state也初始化為N(註意N要與 線程個數一致)。這 N 個子線程是並行執行的,每個子線程執行完後 countDown()一次,state 會CAS減1。等到所有子線程都執行完後(即state=0),會unpark()主調用線程,然後主調用線程 就會從await()函數返回,繼續後餘動作。
2. 說一下自旋鎖的原理
如果持有鎖的線程能在很短的時間內釋放鎖資源,那麼那些等待競爭鎖的線程就不需要做內核態和用戶態之間的切換,它們只需要等一等(自旋),等待鎖釋放之後即可立即獲取鎖,這樣避免了用戶線程和內核切換的消耗
線程自旋是需要消耗 cup 的,說白了就是讓 cup 在做無用功,如果一直獲取不到鎖,那線程 也不能一直占用cup自旋做無用功,所以需要設定一個自旋等待的最大時間。
如果持有鎖的線程執行的時間超過自旋等待的最大時間扔沒有釋放鎖,就會導致其它爭用鎖 的線程在最大等待時間內還是獲取不到鎖,這時爭用線程會停止自旋進入阻塞狀態。自旋鎖的優缺點
自旋鎖儘可能的減少線程的阻塞,這對於鎖的競爭不激烈,且占用鎖時間非常短的代碼塊來 說性能能大幅度的提升,因為自旋的消耗會小於線程阻塞掛起再喚醒的操作的消耗,這些操作會 導致線程發生兩次上下文切換!
但是如果鎖的競爭激烈,或者持有鎖的線程需要長時間占用鎖執行同步塊,這時候就不適合 使用自旋鎖了,因為自旋鎖在獲取鎖前一直都是占用 cpu 做無用功,占著 XX 不 XX,同時有大量 線程在競爭一個鎖,會導致獲取鎖的時間很長,線程自旋的消耗大於線程阻塞掛起操作的消耗, 其它需要cup的線程又不能獲取到cpu,造成cpu的浪費。所以這種情況下我們要關閉自旋鎖;
3. synchronized中類鎖和對象鎖的區別
這麼基礎的一個問題我竟然遺忘了。。因為之前記的是鎖static方法和非static方法的區別,其實鎖static方法就相當於鎖類class,因為static方法是所有的類共用的,類鎖是鎖當前類的所有實例,對象鎖是鎖當前實例對象,詳細的引入別人的博客:Java鎖Synchronized對象鎖和類鎖的區別
4. volatile關鍵字的作用
問:說下項目中用到的設計模式
答:單例模式、責任鏈模式、工程模式、模板模式等等
問:那你說下你們平常單例模式怎麼實現的
答:我們用的是雙向檢查
問:你們雙向檢查有用到volatile吧,那你說下這個關鍵字有什麼作用
答:它可以用來保證線程每次獲取的對象都是最新狀態
問:除此之外還有什麼作用
答: 卒
真的是