JAVA多線程與鎖機制

JAVA多線程與鎖機制

1 關於Synchronized和lock

  • synchronized是Java的關鍵字,當它用來修飾一個方法或者一個代碼塊的時候,能夠保證在同一時刻最多只有一個線程執行該段代碼

    • JDK1.5以後引入了自旋鎖、鎖粗化、輕量級鎖,偏向鎖來有優化關鍵字的性能。
    • 一個線程訪問一個被synchronized修飾的代碼塊,會自動獲取對應的一個鎖,並在執行該代碼塊時,其他線程想訪問這個代碼塊,會一直處於等待狀態,自有等該線程釋放鎖後,其他線程進行資源競爭,競爭獲取到鎖的線程才能訪問該代碼塊。
  • synchronized在發生異常時,會自動釋放線程佔有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖;

  • Lock可以讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷;

  • 通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。

  • Lock鎖適合大量同步代碼的同步問題,synchronized鎖適合代碼少量的同步問題。總體上來說,在資源競爭不激烈的情形下,性能稍微比synchronized差點。但是資源競爭非常激烈的時候,synchronized的性能會下降很多,而ReentrantLock的性能表現仍然比較穩定。

2 Java 實現線程安全的三種方式

  • 同步代碼塊
synchronized(obj)
{
    //需要被同步的代碼塊
}

obj 稱為同步監視器,也就是鎖,原理是:當線程開始執行同步代碼塊前,必須先獲得對同步代碼塊的鎖定。並且任何時刻只能有一個線程可以獲得對同步監視器的鎖定,當同步代碼塊執行完成後,該線程會釋放對該同步監視器的鎖定。

  • 同步方法
public synchronized void testThread()
{
    //需要被同步的代碼塊
}

不需要再指定同步監視器,這個同步方法(非static方法)無需顯式地指定同步監視器,同步方法的同步監視器就是this,也就是調用該方法的對象。

  • 同步鎖,Lock鎖機制, 通過創建Lock對象,採用lock()加鎖,unlock()解鎖,來保護指定的代碼塊。其中,為了確保能夠在必要的時候釋放鎖,代碼中使用finally來確保鎖的釋放,來防止死鎖!

3 Synchronized

  • static synchronized則是該類的所有實例公用一個監視塊,靜態同步方法和非靜態同步方法持有的是不同的鎖,前者是類鎖,後者是對象鎖

  • 1.方法聲明時使用:線程獲得的是成員鎖

    2.對某一代碼塊使用:線程獲得的是成員鎖

    3.synchronized後面括號里是一對象,此時,線程獲得的是對象鎖

    4.synchronized後面括號里是類,此時,線程獲得的是對象鎖

4 樂觀鎖、悲觀鎖

  • 樂觀鎖:總是假設最好的情況,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據。

    • 可以使用版本號機制(+遞歸)和CAS算法實現。如果發現數據已經被更改(通過版本號控制),則不更新數據,再次去重複 所需操作直到道沒有衝突(使用遞歸算法)。
  • 悲觀鎖:總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖。

4 公平鎖、非公平鎖

公平鎖:多個線程按照申請鎖的順序去獲得鎖,線程會直接進入隊列去排隊,永遠都是隊列的第一位才能得到鎖。

  • 優點:所有的線程都能得到資源,不會餓死在隊列中。
  • 缺點:吞吐量會下降很多,隊列裏面除了第一個線程,其他的線程都會阻塞,cpu喚醒阻塞線程的開銷會很大

非公平鎖:多個線程去獲取鎖的時候,會直接去嘗試獲取,獲取不到,再去進入等待隊列,如果能獲取到,就直接獲取到鎖。

  • 優點:可以減少CPU喚醒線程的開銷,整體的吞吐效率會高點,CPU也不必取喚醒所有線程,會減少喚起線程的數量。
  • 缺點:你們可能也發現了,這樣可能導致隊列中間的線程一直獲取不到鎖或者長時間獲取不到鎖,導致餓死

5 JAVA線程池

  • 如果並發的線程數量很多,並且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效率,因為頻繁創建線程和銷毀線程需要時間。線程池使得線程可以復用,就是執行完一個任務,並不被銷毀,而是可以繼續執行其他的任務。

  • 提高系統響應速度,降低系統資源消耗

  • 參數:

    • 一個任務被提交到線程池以後,首先會找有沒有空閑存活線程(>=corePoolSize的情況)

      • 如果有則直接將任務交給這個空閑線程來執行,
      • 如果沒有則會緩存到工作隊列中,如果工作隊列滿了,才會創建一個新線程,然後從工作隊列的頭部取出一個任務交由新線程來處理,而將剛提交的任務放入工作隊列尾部。線程池不會無限制的去創建新線程,它會有一個最大線程數量的限制,這個數量即由maximunPoolSize指定。
    • corePoolSize(核心線程數):

      • 核心線程會一直存活,即使沒有任務需要執行
      • 當線程數小於核心線程數時,即使有線程空閑,線程池也會優先創建新線程處理
    • maxPoolSize(最大線程數):

      • 當線程數=maxPoolSize,且任務隊列已滿時,線程池會拒絕處理任務而拋出異常
    • keepAliveTime(線程存活保持時間):

      • 當線程池中線程數大於核心線程數時,線程的空閑時間如果超過線程存活時間,那麼這個線程就會被銷毀,直到線程池中的線程數小於等於核心線程數。
    • workQueue (工作隊列):

      新任務被提交後,會先進入到此工作隊列中,任務調度時再從隊列中取出任務。jdk中提供了四種工作隊列:

    • handler(線程飽和策略):

      • 當線程池和隊列都滿了,再加入線程會執行此策略。

6 多線程中的i++線程安全嗎

不安全。i++不是原子性操作。i++分為讀取i值,對i值加一,再賦值給i++,執行期中任何一步都是有可能被其他線程搶佔的。

Tags: