1.Synchronized的作用:
能夠保證在同一時刻最多只有一個線程執行該段代碼,以達到保證並發安全的效果。
2.地位:
1)Synchronized是java的關鍵字,並java的怨言原生支持;
2)最基礎的互斥同步手段;
3)並發編程中的元老級角色,是並發編程的必學內容。
3.不使用並發手段會有什么后果?
(1)兩個線程同時a++,最后結果會比預想的少
原因:count++實際上是有3個操作完成:
1)讀取count;
2)將count加一;
3)將count的值寫入到內存中。
4.Synchronized的兩個用法:
(1)對象鎖:包括方法鎖(默認鎖對象為this當前實例對象)和同步代碼塊鎖(自己指定鎖對象);
(2)類鎖:指synchronized修飾靜態的方法或指定鎖為Class對象。
5.synchronized類鎖
概念:Java類可能有很多個對象,但只有一個Class對象,類鎖時Class對象的鎖(類鎖只能在同一時刻被一個對象擁有)
形式:
1)synchronized加在static方法上;
2)synchronized(*.class)代碼塊。
6.多線程訪問同步方法的7種情況:
1)兩個線程同時訪問一個對象的同步方法;
解釋:對象所。會相互等待,只能有一個線程持有鎖。
2) 兩個線程訪問的是兩個對象的同步方法;
解釋:對象鎖。不同的對象實例,擁有不同的對象鎖,互不影響並發執行。
3) 兩個線程訪問的是synchronized的靜態方法;
解釋:類鎖。
4) 同時訪問同步方法與非同步方法;
解釋:synchronized關鍵字只作用於當前方法,不會影響其他未加關鍵字的方法的並發行為。因此非同步方法不受到影響,還是會並發執行。
5) 訪問同一個對象的不同的普通同步方法;
解釋:對象鎖。
synchronized關鍵字雖然沒有指定所要的那個鎖對象,但是本質上是指定了this這個對象作為它的鎖。所以對於同一個實例來講,兩個方法拿到的是同一把鎖,因此會出現串行的情況。
6) 同時訪問靜態synchronized和非靜態的synchronized方法;
解釋:前者為類鎖,鎖為Class類;后者為對象鎖,鎖為this對象。因此兩者的鎖不同,會並行執行。
7) 方法拋異常后,會釋放鎖。
特殊:Lock類加鎖時,如果出現異常,不顯式手動釋放鎖的話,Lock是不會釋放的。
而synchronized不同,一旦出現異常,會自動釋放鎖。
也就是說當第二個線程等待一個被synchronized修飾的方法時,若第一個線程出現異常退出,這把鎖會立刻釋放並且被第二個線程所獲取到。JVM會自動把鎖釋放。
8)擴展:線程進入到一個被synchronized修飾的方法,而在這個方法里面調用了另外一個沒有被synchronized修飾的方法,這個時候還是線程安全的嗎?
答案:不是的。出了本方法后,由於另外的方法沒有被synchronized修飾,所以說這個方法可以被多個線程同時訪問的。
7.synchronized核心思想總結:
1)一把鎖同時只能被一個線程獲取,沒有拿到鎖的線程只能等待(對應1,5);
2)每個實例對應自己的一把鎖,不同實例對應不同的鎖,相互不影響,可以並行。例外:如果鎖是*.class以及synchronized修飾的是static方法時,即類鎖時,所有對象共用一把鎖(對應2,3,4,6)
3)無論是正常執行還是拋出異常,都會釋放鎖(對應7)
8.syscronized性質(可重入,不可中斷)
1)可重入:一個線程拿到了鎖,這個線程可以再次使用該鎖對其他方法,說明該鎖是可以重入的;
2)不可重入:一個線程拿到鎖了,如果需要再次使用該鎖,必須先釋放該鎖才能再次獲取。
可重入鎖的好處:
1)避免死鎖 2)提升封裝性
粒度:
可重入的特性是線程級別的,不是調用級別的(pthread線程)。
問題:為什么synchronized具有可重入性?
答:指的是同一線程的外層函數獲得鎖之后,內層函數可以直接再次獲取該鎖(可避免死鎖,鎖方法1在內部訪問鎖方法2,用的是同一把鎖)。
什么樣的可重入?
1)同一個方法是可重入的;
2)可重入不要求是同一個方法;
3)可重入不要求是同一個類中的。
synchronized的性質:不可中斷性質
1)線程A拿到鎖,不釋放的話,別人永遠拿不到鎖,永遠等待;
2)Lock鎖會有一些比較靈活的功能,按時間等。
加鎖和釋放鎖的原理:
現象:
每個類的實例對應着一把鎖,每個syncronized方法首先必須獲得調用該方法實例的鎖,才能執行;否則,線程只能被阻塞。方法一旦執行,便獨占了該把鎖。直到該方法執行結束返回或者拋出異常,才將該鎖釋放。鎖釋放之后,其他阻塞鎖才能競爭獲取該把鎖。
當一個對象中有synchronized修飾的方法或者代碼塊的時候,要想執行這段代碼,就必須先獲得這個對象鎖,如果此對象的對象鎖已經被其他調用者所占用,就必須等待它被釋放。所有的Java對象都含有一個互斥鎖,這個鎖由JVM自動去獲取和釋放,我們只需要指定這個對象就行了,至於鎖的釋放和獲取不 需要我們操心。
獲取和釋放鎖的時機:內置鎖(監視器鎖)
線程在進入同步代碼塊之前,會自動獲取該鎖,並且退出代碼塊時會自動釋放該鎖。無論是正常退出或者拋出異常退出,都會釋放鎖。
然而獲取鎖的唯一途徑:進入這個鎖保護的同步代碼塊或者同步方法中。
Jvm字節碼:
1)將Java文件編程為 .class文件:javac xxx.java;
2)通過反編譯查看字節碼,javap -verbose xxx.class;
3)synchronized如何實現的,有個加鎖monitorenter和解鎖monitorexit讀到該指令,會讓monitor計數器+1或-1。
注意點:線程既可以在方法完成之后退出,也可以在拋出異常后退出,因此monitorexit數量多於monitorenter。
可重入原理:(加鎖次數計數器)
1)jvm負責跟蹤對象被加鎖的次數。
2)線程第一次給對象加鎖的時候,計數變為1.每當這個相同線程在此對象上再次獲得鎖時,計數會遞增。
3)每當任務離開時,計數遞減,當計數為0時,鎖被完全釋放。
(1)可重入:如果線程已拿到鎖之后,還想再次進入由這把鎖所控制的方法中,而無需提前釋放,可以直接進入。
(2)可重入:指的是同一線程的外層函數獲得鎖之后,內層函數可以直接再次獲取該鎖。也叫做遞歸鎖。Java中兩大遞歸鎖:Synchronized和ReentrantLock。
可見性原理:java內存模型
線程A向線程B發送數據的兩個步驟:
1)線程A修改了本地內存A,並將其存儲到主內存中。
2)線程B再從主內存中讀取出來。
這個過程是由JMM(Java Memory Model)控制的。JMM通過控制主內存與每個線程的本地內存的交互來為Java程序員提供內存可見性的保證。
synchronized是如何做到內存可見性的實現?
一旦一個代碼塊或者方法被synchronized修飾之后,那么它在執行完畢之后被鎖住的對象所做的任何修改都要在釋放鎖之前從線程內存寫回到主內存 中。所以下一個線程從主內存中讀取到的數據一定是最新的。就是通過這樣的原理,synchronized關鍵字保證了每一次執行都是可靠的,保證了可見性。
9.Synchronized的缺陷
1)效率低:
鎖的釋放情況少;試圖獲得鎖時不能設定超時;不能中斷一個正在試圖獲得鎖的線程。
2)不夠靈活(讀寫鎖更靈活:讀操作的時候不會加鎖,寫操作的時候才會加鎖):
加鎖和釋放的時機單一;每個鎖僅有單一的條件(某個對象),可能是不夠的。
3)無法知道是否成功獲取到鎖。
但是,lock有一些不一樣的特性:
Lock可以嘗試成功了做一些邏輯判斷,如果沒有成功做另外一些邏輯判斷.
Lock類:
lock.lock();lock.unlock();
通過這兩個方法,可以手動加鎖和解鎖。
lock.tryLock();lock.tryLock(10, TimeUnit.MINUTES);
可以判斷是否加鎖,返回類型為boolean
補充重點:
1.synchronized的使用注意點:
鎖對象不能為空:鎖的信息保存在對象頭里面作用域不宜過大:synchronized關鍵字包裹的范圍。
不需要串行工作的情況下,用並行的方式可以提高運行的效率避免死鎖。
2.如何選擇Lock和synchronized關鍵字?
1)如果可以的情況下,兩者都不要選擇,而是使用java.util.concurrent包中的各種各樣的類,例如:CountDownLatch等。使用這些類,不需要自己做同步工作,更方便,也更不容易出錯。
2)如果synchronized關鍵字在程序中適用,就優先實用這個關鍵字。因為這樣可以減少需要編寫的代碼,就減少了出錯的幾率。
3)如果特別需要Lock這樣結構獨有的特性的時候,才使用Lock。
以上三點主要是基於減少代碼出錯為出發點。
10.思考題
1)多個線程等待同一個synchronized鎖的時候,JVM如何選擇下一個獲取鎖的是哪個線程?
鎖調度機制。對於synchronized內置鎖,不同版本的JVM處理方式不同,blocked和running都有幾率。
2)synchronized使得同時只有一個線程可以執行,性能較差,有什么辦法可以提升性能?
(1)優化使用范圍,讓加鎖區在業務允許的情況下足夠小。
(2)用其他類型的鎖,例如讀寫鎖,這樣在讀的時候就不止一個線程可以同時進入代碼。
3)我想更靈活地控制鎖的獲取和釋放(現在釋放鎖的時機都被規定死了),怎么辦?
自己實現一個Lock
4)什么是鎖的升級、降級?什么是JVM里的偏斜鎖、輕量級鎖、重量級鎖?
在之前的JVM版本中,synchronized性能不是特別的好,而經過不斷的迭代,synchronized性能已經得到了顯著的提高,這里面運用的技術就是偏斜鎖、輕量級鎖、重量級鎖。JVM會根據synchronized關鍵字使用到的次數或者其他的種種指標對鎖進行有效的優化使得性能得到大幅上漲,這里面還涉及到了對象頭里面的字段。