您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“Java同步鎖synchronized怎么使用”,內容詳細,步驟清晰,細節處理妥當,希望這篇“Java同步鎖synchronized怎么使用”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
線程安全是Java并發編程中的重點,而造成線程安全問題的主要原因有兩點,一是存在共享數據(也稱臨界資源),二是存在多條線程共同操作共享數據。因此,當存在多個線程操作共享數據時,需要保證同一時刻有且只有一個線程在操作共享數據,其他線程必須等到該線程處理完數據后再進行,這種方式就叫互斥鎖。也就是說當一個共享數據被正在訪問的線程加上互斥鎖后,在同一個時刻,其他線程只能處于等待的狀態,直到當前線程處理完畢釋放該鎖。在 Java 中,關鍵字 synchronized可以保證在同一個時刻,只有一個線程可以執行某個方法或者某個代碼塊(主要是對方法或者代碼塊中存在共享數據的操作),同時synchronized還有另外一個重要的作用,它可以可保證一個線程的變化(主要是共享數據的變化)被其他線程所看到(保證可見性,完全可以替代Volatile功能)。
synchronized是Java的關鍵字,是一種同步鎖。
Java的內置鎖:每個java對象都可以用做一個實現同步的鎖,這些鎖稱為內置鎖。線程進入同步代碼塊或方法的時候會自動獲得該鎖,在退出同步代碼塊或方法時會釋放該鎖。獲得內置鎖的唯一途徑就是進入這個鎖的保護的同步代碼塊或方法。
Java內置鎖是一個互斥鎖,這就是意味著最多只有一個線程能夠獲得該鎖,當線程A嘗試去獲得線程B持有的內置鎖時,線程A必須等待或者阻塞,直到線程B釋放這個鎖。
Java的對象鎖和類鎖:java的對象鎖和類鎖在鎖的概念上基本上和內置鎖是一致的,但是兩個鎖實際是有很大的區別的,對象鎖是用于對象實例方法,或者一個對象實例上的,類鎖是用于類的靜態方法或者一個類的class對象上的。
在Java中,每個對象都有一把鎖和兩個隊列,一個隊列用于掛起未獲得鎖的線程,一個隊列用于掛起條件不滿足而等待的線程。而synchronized實際上也就是一個加鎖和釋放鎖的集成。JVM負責跟蹤對象被加鎖的次數。如果一個對象被解鎖,其計數變為0。在任務(線程)第一次給對象加鎖的時候,計數變為1。每當這個相同的任務(線程)在此對象上獲得鎖時,計數會遞增。只有首先獲得鎖的任務(線程)才能繼續多次獲取該對象上的鎖。每當任務離開一個synchronized方法,計數遞減,當計數為0的時候,鎖被完全釋放,此時別的任務就可以使用此資源。
synchronized可以修飾范圍的包括:方法級別,代碼塊級別;而實際加鎖的目標包括:對象鎖(普通變量,靜態變量),類鎖。具體分為三種應用方式:
被修飾的方法稱為實例同步方法,其作用范圍是整個方法,鎖定的是該方法所屬的對象(即調用該方法的對象)。所有需要獲得該對象鎖的操作都會對該對象加鎖(即訪問該對象的其他同步實例方法或進入對該對象加鎖的代碼塊)。實例同步方法的代碼如下:
public synchronized void method(){ // 具體代碼 }
當一個對象O1在不同的線程中執行這個同步方法時,他們之間會形成互斥,達到同步的效果。但是這個對象所屬類的另一對象O2卻能夠調用這個被加了synchronized關鍵字的方法。 每個對象實例對應一把鎖,線程只有獲得對象實例的鎖才能執行它的synchronized方法。 如果一個對象有多個synchronized方法,只要一個線程訪問了其中的一個synchronized方法,其它線程不能同時訪問這個對象中任何一個synchronized方法。但是該類的其他對象實例的 synchronized方法是不相干擾的。這種機制確保了同一時刻對于每一個對象實例,其所有聲明為 synchronized 的成員方法中至多只有一個處于可執行狀態(因為至多只有一個能夠獲得該類實例對應的鎖),從而有效避免了類成員變量的訪問沖突(只要所有可能訪問類成員變量的方法均被聲明為synchronized)。上邊的示例代碼等同于如下代碼:
public void method(){ synchronized(this){ //具體代碼 } }
其中this指的是調用這個方法的對象,如O1。可見同步方法實質是將synchronized作用于對象引用。只有獲得O1對象鎖的線程,才能夠調用O1的同步方法,而對O2而言,O1對象鎖和它互不關聯,其他線程調用O2中的相同方法時,并不會產生同步阻塞。程序也可能在這種情形下擺脫同步機制的控制,造成數據混亂。sychronized修飾方法時需要注意以下3點:
(1)synchronized關鍵字不能繼承。
雖然可以使用synchronized來定義方法,但synchronized并不屬于方法定義的一部分,因此,synchronized關鍵字不能被繼承。如果在父類中的某個方法使用了synchronized關鍵字,而在子類中覆蓋了這個方法,在子類中的這個方法默認情況下并不是同步的,必須顯式地在子類為這個方法加上synchronized關鍵字才可以。當然,還可以在子類方法中調用父類中相應的方法,這樣雖然子類中的方法不是同步的,但子類調用了父類的同步方法,因此,子類的方法也就相當于同步了。這兩種方式的示例代碼如下:
  手動加上synchronized修飾
class Parent{ public synchronized void method() {} } class Child{ public synchronized void method() {} } ??在子類中調用父類同步方法
class Parent{ public synchronized void method() {} } class Child{ public synchronized void method() {} }
(2)在定義接口方法時不能使用synchronized關鍵字。
(3)構造方法不能使用synchronized關鍵字,但可以使用synchronized代碼塊來進行同步。
被修飾的方法被稱為靜態同步方法,其作用的范圍是整個靜態方法,鎖是靜態方法所屬的類(即Class對象)。所有需要獲得該類的任意對象的鎖,都會觸發同步。靜態同步方法的示例如下圖:
上述代碼中,雖然創建了SynThread類的兩個對象,但是該類中的run方法調用的是靜態同步方法,所以在運行過程中會同步執行。因此,synchronized作用在靜態方法上時,可以防止多個線程同時訪問這個類中的靜態方法,它對類的所有實例對象都起作用。
被修飾的代碼塊稱為同步語句塊。synchronized的括號中必須傳入一個對象(實例對象或類的Class對象)作為鎖。其作用范圍是大括號{}括起來的代碼,鎖是Synchronized括號里指定的內容。按照對象的類型可以分為類鎖和對象鎖。
(1)鎖對象為實例對象
public void method(Object o) { synchronized(o) { ... } }
上述代碼鎖定的就是o這個對象,只要進入以該對象為鎖的任何代碼都會觸發同步。當有一個明確的對象作為鎖時,可以直接以該對象作為鎖。當沒有明確的對象作為鎖,只是想讓一段代碼同步時,可以創建一個特殊的對象來充當鎖。例如:
private byte[] lock = new byte[0];
注:查看編譯后的字節碼:生成零長度的byte[]對象只需3條操作碼,而Object lock = new Object()則需要7行操作碼。因此使用特殊對象來充當鎖,大大節省了系統的開銷。
public class Demo{ ... public static void method(){ synchronized(Demo.class){ ... } } }
上述代碼是以Demo類的Class對象為鎖,進入以該類任意實例對象為鎖的代碼都會觸發同步,其效果類似于靜態同步方法。
monitor對象
Java中的同步代碼塊是使用monitorenter和monitorexit指令實現的,其中monitorenter指令插入到同步代碼塊的開始位置,monitorexit指令插入到同步代碼塊的結束位置。JVM保證每一個monitorenter都有一個monitorexit與之相對應。任何對象都有一個monitor與之相關聯,當線程執行到monitorenter指令時,將會嘗試獲取鎖對象所對應的monitor所有權,即嘗試獲取對象的鎖;當線程執行monitorexit指令時,鎖的monitor就會被釋放。同步方法的實現與同步塊略有不同,它依靠的是方法修飾符上的ACC_SYNCHRONIZED實現。synchronized具體的實現原理詳見本人另一篇文章:
深入理解Java中Synchronized的實現原理
(1) 相對于ReentrantLock而言,synchronized鎖是重量級鎖,重量級體現在活躍性差一點。同時synchronized鎖是內置鎖,意味著JVM能基于synchronized鎖做一些優化:比如增加鎖的粒度(鎖粗化)、鎖消除。
(2) 在synchronized鎖上阻塞的線程是不可中斷的:線程A獲得了synchronized鎖,當線程B也去獲取synchronized鎖時會被阻塞。而且線程B無法被其他線程中斷(不可中斷的阻塞),而ReentrantLock鎖能實現可中斷的阻塞。
(3) synchronized鎖釋放是自動的,當線程執行退出synchronized鎖保護的同步代碼塊時,會自動釋放synchronized鎖。而ReentrantLock需要顯示地釋放:即在try-finally塊中釋放鎖。
(4) 線程在競爭synchronized鎖時是非公平的:假設synchronized鎖目前被線程A占有,線程B請求鎖未果,被放入隊列中,線程C請求鎖未果,也被放入隊列中,線程D也來請求鎖,恰好此時線程A將鎖釋放了,那么線程D將跳過隊列中所有的等待線程并獲得這個鎖。而ReentrantLock能夠實現鎖的公平性。
(5) synchronized鎖是讀寫互斥并且讀讀也互斥,ReentrantReadWriteLock 分為讀鎖和寫鎖,而讀鎖可以同時被多個線程持有,適合于讀多寫少場景的并發。
(6) ReentrantLock鎖的是代碼塊,synchronized還能鎖方法和類。ReentrantLock可以知道線程有沒有拿到鎖,而synchronized不能。
讀到這里,這篇“Java同步鎖synchronized怎么使用”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。