Java高併發綜合

曾經,我在面試Java研發實習生時最常聽到的一句話就是:java

搞Java怎麼能不學併發呢?程序員

沒錯,真的是通過了面試官的無數鄙視,我才知道Java併發編程在Java語言中的重要性。面試

併發模型

悲觀鎖和樂觀鎖的理解及如何實現,有哪些實現方式?

悲觀鎖

悲觀鎖假設最壞的狀況(若是你不鎖門,那麼搗蛋鬼就會闖入並搞得一團糟),而且只有在確保其餘線程不會干擾(經過獲取正確的鎖)的狀況下才能執行下去。算法

常見實現如獨佔鎖等。編程

安全性更高,但在中低併發程度下的效率更低。安全

樂觀鎖

樂觀鎖藉助衝突檢查機制來判斷在更新過程當中是否存在其餘線程的干擾,若是存在,這個操做將失敗,而且能夠重試(也能夠不重試)。併發

常見實現如CAS等。函數

部分樂觀鎖削弱了一致性,但中低併發程度下的效率大大提升。性能

併發編程

Java中如何建立一個線程

從面相接口的角度上講,實際上只有一種方法實現Runable接口;但Thread類爲線程操做提供了更多的支持,因此一般作法是實現Runable接口,實例化並傳入Thread類的構造函數。this

  1. 繼承Thread,覆寫run方法
  2. 實現Runable接口,覆寫run方法

Vector(HashTable)如何實現線程安全

經過synchronized關鍵字修飾每一個方法。

依據synchronized關鍵字引伸出如下問題。

synchronized修飾方法和修飾代碼塊時有何不一樣

持有鎖的對象不一樣:

  1. 修飾方法時:this引用的當前實例持有鎖
  2. 修飾代碼塊時:要指定一個對象,該對象持有鎖

從而致使兩者的意義不一樣:

  1. 同步代碼塊在鎖定的範圍上可能比同步方法要小,通常來講鎖的範圍大小和性能是成反比的。
  2. 修飾代碼塊能夠選擇對哪一個對象加鎖,可是修飾方法只能給this對象加鎖。

ConcurrentHashMap的如何實現線程安全

ConcurrentHashMap的線程安全實現與HashTable不一樣:

  1. 能夠將ConcurrentHashMap理解爲,不直接持有一個HashMao,而是用多個Segment代替了一個HashMap。但實際實現的Map部分和HashMap的原理基本相同,對腳標取模來肯定table[i]所屬段,從而對不一樣的段獲取不一樣的段鎖。
  2. 每一個Segment持有一個鎖,經過分段加鎖的方式,既實現了線程安全,又兼顧了性能

Java中有哪些實現併發編程的方法

要從最簡單的答起,業界最經常使用的是重點,有新意就放在最後。

  1. synchronized關鍵字
  2. 使用繼承自Object類的wait、notify、notifyAll方法
  3. 使用線程安全的API和集合類:
    1. 使用Vector、HashTable等線程安全的集合類
    2. 使用Concurrent包中提供的ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentLinkedQueue等弱一致性的集合類
    3. 在Collections類中有多個靜態方法,它們能夠獲取經過同步方法封裝非同步集合而獲得的集合,如List list = Collection.synchronizedList(new ArrayList())
    4. 使用原子變量、volatile變量等
  4. 使用Concurrent包中提供的信號量Semaphore、閉鎖Latch、柵欄Barrier、交換器Exchanger、Callable&Future、阻塞隊列BlockingQueue等.
  5. 手動使用Lock實現基於鎖的併發控制
  6. 手動使用Condition或AQS實現基於條件隊列的併發控制
  7. 使用CAS和SPIN等實現非阻塞的併發控制
  8. 使用不變類
  9. 其餘併發模型尚未涉及

從而引伸出以下問題:

ConcurrentHashMap的的實現原理(見前)

CopyOnWriteArrayList的複製操做發生在什麼時機

synchronizedList&Vector的區別

  1. synchronizedList的實現中,synchronized關鍵字修飾代碼塊;Vector的實現中修飾方法。
  2. synchronizedList只封裝了add、get、remove等代碼塊,但Iterator卻不是同步的,進行遍歷時要手動進行同步處理;Vector中對Iterator也進行了加鎖。
  3. synchronizedList可以將全部List實現類封裝爲同步集合,其內部持有的仍然是List的實現類(ArrayList/LinkedList),因此除同步外,幾乎只有該實現類和Vector的區別。

synchronized修飾方法和修飾代碼塊時有何不一樣(見前)

信號量Semaphore、閉鎖Latch、柵欄Barrier、交換器

Exchanger、Callable&Future、阻塞隊列BlockingQueue的實現原理

ConcurrentLinkedQueue的插入算法

算法核心可歸納爲兩步:

  1. 先檢測是不是中間狀態(SPIN)
  2. 再嘗試CAS插入

詳細待補充。

參考:

在java中wait和sleep方法的不一樣?

最大的不一樣是在等待時wait會釋放鎖,而sleep一直持有鎖。Wait一般被用於線程間交互,sleep一般被用於暫停執行。

爲何wait, notify 和 notifyAll這些方法不在thread類裏面?

主要緣由是JAVA提供的鎖是對象級的而不是線程級的,每一個對象都有鎖,經過線程得到。因爲wait,notify和notifyAll都是鎖級別的操做,因此把他們定義在Object類中由於鎖屬於對象。

爲何wait和notify方法要在同步塊中調用?

Java API強制要求這樣作,若是你不這麼作,你的代碼會拋出IllegalMonitorStateException異常。還有一個緣由是爲了不wait和notify之間產生競態條件。

爲何你應該在循環中檢查等待條件?

處於等待狀態的線程可能會收到錯誤警報和僞喚醒,若是不在循環中檢查等待條件,程序就會在沒有知足結束條件的狀況下退出。

Java線程池中submit() 和 execute()方法有什麼區別?

兩個方法均可以向線程池提交任務,execute()方法的返回類型是void,它定義在Executor接口中, 而submit()方法能夠返回持有計算結果的Future對象,它定義在ExecutorService接口中,它擴展了Executor接口,其它線程池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。

volatile 變量和 atomic 變量有什麼不一樣?

Volatile變量能夠確保先行關係,即寫操做會發生在後續的讀操做以前, 但它並不能保證原子性。例如用volatile修飾count變量那麼 count++ 操做就不是原子性的。而AtomicInteger類提供的atomic方法可讓這種操做具備原子性如getAndIncrement()方法會原子性的進行增量操做把當前值加一,其它數據類型和引用變量也能夠進行類似操做。

爲何Thread類的sleep()和yield ()方法是靜態的?

Thread類的sleep()和yield()方法將在當前正在執行的線程上運行。因此在其餘處於等待狀態的線程上調用這些方法是沒有意義的。這就是爲何這些方法是靜態的。它們能夠在當前正在執行的線程中工做,並避免程序員錯誤的認爲能夠在其餘非運行線程調用這些方法。

相關文章
相關標籤/搜索