JAVA 並發編程Synchronized鎖的是什麼?

2020-12-09 計算機java編程

臨界區與鎖

並發編程中不可避免的會出現多個線程共享同一個資源的情況,為了防止出現數據不一致情況的發生,人們引入了臨界區的概念。臨界區是一個用來訪問共享資源的代碼塊,同一時間內只運行一個線程進入。

那麼如何實現這個臨界區呢?這就用到我們的鎖了,當進程想要訪問一個臨界區時,它先會去看看是否已經有其他線程進入了,也就是看是否能獲得鎖。如果沒有其他線程進入,那麼它就進入臨界區,其他線程就無法進入,相當於加鎖。反之,則會被掛起,處於等待狀態,直到其他線程離開臨界區,且本線程被JVM選中才可進入(因為可能有其他線程也在等待)。

利用Synchronized解決並發問題

Synchronize是一個重量級鎖,它會降低程序性能,因此如果對數據一致性沒有要求,就不要使用它。如果方法被Synchronize關鍵字聲明,那麼該方法的代碼塊被視為臨界區。當某個線程調用該對象的synchronized方法或者訪問synchronized代碼塊時,這個線程便獲得了該對象的鎖,其他線程暫時無法訪問這個方法,只有等待這個方法執行完畢或者代碼塊執行完畢,這個線程才會釋放該對象的鎖,其他線程才能執行這個方法或者代碼塊。

下面我們將創建兩個線程A,B來同時訪問一個對象:A從帳戶裡取錢,B從帳戶裡存錢。首先是不使用Synchronized關鍵字。

創建帳戶類

它擁有一個私有變量balance表示金額,addAmount和subtractAmount分別對金額執行加減操作。

創建A,B倆線程,分別對帳戶存錢和取錢。

最後在main裡面測試

ThreadA往帳戶中執行了10次存入操作,每次存入1000元,ThreadB則是以同樣的金額執行了10次取出操作。那麼按照我們的推測,最後帳戶的金額應該維持不變,但程序的結果卻不是我們想要的數字。這是為什麼呢?因為我們在對數據進行操作的時候,另外一個線程可能也在進行操作,邏輯上應該先後執行的方法變成了同時執行,所以出現了錯誤。

現在我們給addAmount和subtractAmount加上synchronized關鍵字,保證數據一致性,這樣程序就不會出問題了。

如果是使用synchronize保護代碼塊,則需要將對象引用作為參數傳入。一般來說傳入this關鍵字作為引用執行方法的對象就可以了。

鎖的到底是什麼?

或許在上面的例子你因為粗心只為其中一個方法加了關鍵字,那麼你會看到這樣的現象:

保護代碼塊要將對象傳入,那應該鎖的是對象呀。你可能會想:我執行subtractAmout,按道理應該等我執行完addAmount才能執行,它都沒有account這個對象的鎖,不應該在中間插這麼一段呀。但是,只有加了鎖的方法,線程執行該方法時才會去嘗試獲得鎖,看看是否有線程進入臨界區。訪問非同步方法無需獲得鎖,你把synchronized去掉跟你只加一個的情況是一樣的,同步方法與非同步遵循的是不同的規則。也就是說你可以在調用該對象的加了synchronized方法的同時,調用其他的非同步方法。

兩個線程怎麼同時訪問了同一個對象的兩個synchronized方法?

你可能在搗鼓這個關鍵字的時候,驚訝地發現靜態方法的與眾不同。如果一個對象中的靜態方法用synchronized修飾,那麼其他線程可以在該靜態方法被訪問的同時,訪問該對象中的非靜態方法(當然,該靜態方法同一時間只能被一個線程訪問)。換句話說,兩個線程可以同時訪問一個對象中的兩個synchronized方法。

等等,不是說鎖對象嗎?到底鎖的是什麼?鎖的確實是對象,但對於靜態方法我們說的是T.class(T 為類名),非靜態方法鎖的是this ,也就是類的實例對象,兩者是不同的。

上面那段代碼相當於:

實際上加鎖本質就是在鎖對象的對象頭中寫入當前線程id。我們可以通過下面的代碼驗證,每次都傳入new Object()。

因為線程每次調用方法鎖的都是新new的對象,所以加鎖無效。甚至編譯器可能會將synchronized給優化掉,因為這相當於多把鎖保護同一個資源,編譯器一看,每個人都弄把鎖就進來了,那我還不如不加,反正都一個樣。

另外需要注意的是,synchronized是可重入鎖。也就是說當線程訪問對象的同步方法時,在調用其他同步方法時無需再去獲取其訪問權。因為我們實際上鎖的是對象,對象頭裡面記錄的都是當前線程的ID。

總結

修飾函數,鎖的是當前類的實例化對象修飾靜態方法,鎖的是當前類的Class對象修飾同步代碼塊,鎖的是括號裡的對象加鎖實際上就是在鎖對象的對象頭中寫入當前線程id,每個線程要想調用這個同步方法,都會先去鎖對象的對象頭看看當前線程id是不是自己的。

相關焦點

  • Java並發編程:synchronized
    雖然多線程編程極大地提高了效率,但是也會帶來一定的隱患。比如說兩個線程同時往一個資料庫表中插入不重複的數據,就可能會導致資料庫中插入了相同的數據。今天我們就來一起討論下線程安全問題,以及Java中提供了什麼機制來解決線程安全問題。以下是本文的目錄大綱:一.什麼時候會出現線程安全問題?二.如何解決線程安全問題?
  • 「從入門到放棄-Java」並發編程-鎖-synchronized
    簡介上篇【從入門到放棄-Java】並發編程-線程安全中,我們了解到,可以通過加鎖機制來保護共享對象,來實現線程安全。synchronized是java提供的一種內置的鎖機制。通過synchronized關鍵字同步代碼塊。
  • Java並發編程之支持並發的list集合你知道嗎
    Java並發編程之-list集合的並發.我們都知道Java集合類中的arrayList是線程不安全的。那麼怎麼證明是線程不安全的呢?怎麼解決在並發環境下使用安全的list集合類呢?本篇是《凱哥(凱哥Java:kagejava)並發編程學習》系列之《並發集合系列》教程的第一篇:本文主要內容:怎麼證明arrayList不是線程安全的?怎麼解決這個問題?以及遇到問題解決的四個步驟及從源碼來分析作者思路。一:怎麼證明arrayList在並發情況下是線程不安全的呢?
  • Java面試熱點學習:深入並發編程中的synchronized(前三章)
    第一章:並發編程中的三個問題可見性學習什麼是可見性問題可見性概念可見性(Visibility):是指一個線程對共享變量進行修改,另一個先立即得到修改後的最新值。小結並發編程時,會出現可見性問題,當一個線程對共享變量進行了修改,另外的線程並沒有立即看到修改後的最新值。
  • Java 並發編程:Synchronized 及其實現原理
    通過這兩段描述,我們應該能很清楚的看出Synchronized的實現原理,Synchronized的語義底層是通過一個monitor的對象來完成,其實wait/notify等方法也依賴於monitor對象,這就是為什麼只有在同步的塊或者方法中才能調用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因。
  • Java並發編程:synchronized關鍵字的實現原理——鎖膨脹過程
    前言上一篇分析了優化後的synchronized在不同場景下對象頭中的表現形式,還記得那個結論嗎?當一個線程第一次獲取鎖後再去拿鎖就是偏向鎖,如果有別的線程和當前線程交替執行就膨脹為輕量級鎖,如果發生競爭就會膨脹為重量級鎖。
  • 「原創」Java並發編程系列09|基礎乾貨
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫本文為何適原創並發編程系列第9篇。現在,我們進入正題:介紹並發編程的基礎性概念。
  • Java面試熱點:深入學習並發編程中的synchronized(後三章)
    小結通過javap反彙編我們看到synchronized使用編程了monitorentor和monitorexit兩個指令.每個鎖對象 都會關聯一個monitor(監視器,它才是真正的鎖對象),它內部有兩個重要的成員變量owner會保存獲得鎖 的線程,recursions會保存線程獲得鎖的次數,
  • 原創】Java並發編程系列01|開篇獲獎感言
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫為什麼要學並發編程我曾聽一個從事15年開發工作的技術人員說過1.並發理論:並發編程要解決的三大問題;介紹可見性與有序性問題的根源重排序;學習Java內存模型(JMM),理解JMM如何解決這些問題以實現並發編程的。2.並發關鍵字:深入volatile、synchronized、final關鍵字的作用,都解決了什麼問題,以及其實現原理。
  • JAVA並發編程:concurrent提供了哪些比synchronized更高效的鎖
    在java.util.locks包下面提供了三種鎖,ReentrantLock、ReentrantReadWriteLock和StampedLock,下面一一講解這三種鎖的特點、實現方式以及應用。tryLock(long time, TimeUnit unit) throws InterruptedException;嘗試獲取鎖,直到超時;void unlock();解鎖操作newCondition()方法;實現等待、通知模式,後面例子中會講解其應用ReentrantLockreentrantLock可以實現synchronized的所有功能
  • Java的synchronized 能防止指令重排序嗎?
    「二胖」:好的,我叫二胖,我來自長沙,今年25歲,從事java開發快3年了,現在在XX公司XX事業部擔任高級「java」開發工程師,主要負責XX系統。。。。。「面試官」:好的,我看你簡歷上寫著熟練掌握並發編程你能跟我說說並發編程裡面你都知道哪些關鍵字。
  • Java synchronized 詳解
    參與構建火車票業務系統的底層技術支持體系,個人對並發編程、分布式系統等技術點感興趣。前言記得剛剛學習Java的時候,遇到多線程情況首先想到的就是synchronized。相對於當時的我們來說,synchronized是那麼的神奇而強大,同時它也成為我們解決多線程情況百試不爽的良藥。
  • JAVA中synchronized與static synchronized 的區別
    是對類的當前實例進行加鎖,防止其他線程同時訪問該類的該實例的所有synchronized塊,注意這裡是「類的當前實例」, 類的兩個不同實例就沒有這種約束了。實際上,在類中某方法或某代碼塊中有 synchronized,那麼在生成一個該類實例後,改類也就有一個監視快,放置線程並發訪問改實例synchronized保護快,而static synchronized則是所有該類的實例公用一個監視快了,也也就是兩個的區別了,也就是synchronized相當於 this.synchronized,而static synchronized相當於Something.synchronized
  • Java多線程synchronized
    沒錯就是使用synchronized。如何解決線程安全問題?那麼一般來說,是如何解決線程安全問題的呢?基本上所有的併發模式在解決線程安全問題時,都採用「序列化訪問臨界資源」的方案,即在同一時刻,只能有一個線程訪問臨界資源,也稱作同步互斥訪問。通常來說,是在訪問臨界資源的代碼前面加上一個鎖,當訪問完臨界資源後釋放鎖,讓其他線程繼續訪問。
  • Java並發包下Java多線程並發之讀寫鎖鎖學習第五篇-讀寫鎖
    Java多線程並發之讀寫鎖本文主要內容:讀寫鎖的理論;通過生活中例子來理解讀寫鎖;讀寫鎖的代碼演示;讀寫鎖總結。通過理論(總結)-例子-代碼-然後再次總結,這四個步驟來讓大家對讀寫鎖的深刻理解。本篇是《凱哥(凱哥Java:kagejava)並發編程學習》系列之《Lock系列》教程的第七篇:《Java並發包下鎖學習第七篇:讀寫鎖》。一:讀寫鎖的理論什麼是讀寫鎖?
  • 【死磕Java並發】深入分析synchronized實現原理
    但是,隨著我們學習的進行我們知道synchronized是一個重量級鎖,相對於Lock,它會顯得那麼笨重,以至於我們認為它不是那麼的高效而慢慢摒棄它。 誠然,隨著Javs SE 1.6對synchronized進行的各種優化後,synchronized並不會顯得那麼重了。下面跟隨LZ一起來探索synchronized的實現機制、Java是如何對它進行了優化、鎖優化機制、鎖的存儲結構和升級過程。
  • 「原創」Java並發編程系列02|並發編程三大核心問題
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫寫在前面編寫並發程序是比較困難的,因為並發程序極易出現Bug,這些Bug有都是比較詭異的,很多都是沒辦法追蹤,而且難以復現。
  • 我和面試官的博弈:Java 並發編程篇
    還請大家置頂(標星)本公眾號:Java後端,第一時間接收優質博面試中問的頻率很高的一個是分布式,一個就是並發。而JUC(java.util.concurrent)裡的東西是並發編程的基石。上次的面試已經過去一段時間,在一邊努力工作的同時,我也一邊抽出時間準備 Java 並發編程的部分。今天懷著輕鬆愉快的心情,再次踏上我的大廠面試之旅。
  • Java經典面試題,你用過synchronized嗎?它的底層原理是什麼?
    作為Java程式設計師,不懂得並發編程顯然已經不能滿足市場需求了,尤其是在面試過程中將處於被動地位,也有可能面試將就此終結。那麼作為Java開發者的你,日常雖然可以基於Java的並發工具包實現並發編程,但它背後的原理和機制你真的明白嗎?不妨來檢驗下自己,對於synchronized關鍵字,你用過嗎?它的底層原理又是什麼呢?
  • 徹底理解Java並發編程原理!
    Java並發編程為四大部分:計算機並發基礎知識、JDK內置並發框架、JDK並發包剖析以及其它並發知識。具體包括線程的狀態、Java線程調度策略、線程優先級、並發模型、悲觀鎖樂觀鎖、JDK各種同步器、JDK內置AQS同步器、線程與IO、Java線程與JVM線程、阻塞與喚醒機制、JDK並發包各種工具剖析、自旋、JDK內置並發鎖、CAS、synchronized、線程池、線程之間的協作等並發方面知識及原理進行深入淺出的講解。