1.Synchronized的做用:java
可以保證在同一時刻最多隻有一個線程執行該段代碼,以達到保證併發安全的效果。程序員
2.地位:編程
1)Synchronized是java的關鍵字,並java的怨言原生支持;安全
2)最基礎的互斥同步手段;多線程
3)併發編程中的元老級角色,是併發編程的必學內容。併發
3.不使用併發手段會有什麼後果?jvm
(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會自動把鎖釋放。
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關鍵字保證了每一次執行都是可靠的,保證了可見性。
1)效率低:
鎖的釋放狀況少;試圖得到鎖時不能設定超時;不能中斷一個正在試圖得到鎖的線程。
2)不夠靈活(讀寫鎖更靈活:讀操做的時候不會加鎖,寫操做的時候纔會加鎖):
加鎖和釋放的時機單一;每一個鎖僅有單一的條件(某個對象),多是不夠的。
3)沒法知道是否成功獲取到鎖。
可是,lock有一些不同的特性:
Lock能夠嘗試成功了作一些邏輯判斷,若是沒有成功作另一些邏輯判斷.
lock.lock();lock.unlock();
經過這兩個方法,能夠手動加鎖和解鎖。
lock.tryLock();lock.tryLock(10, TimeUnit.MINUTES);
能夠判斷是否加鎖,返回類型爲boolean
補充重點:
鎖對象不能爲空:鎖的信息保存在對象頭裏面做用域不宜過大:synchronized關鍵字包裹的範圍。
不須要串行工做的狀況下,用並行的方式能夠提升運行的效率避免死鎖。
1)若是能夠的狀況下,二者都不要選擇,而是使用java.util.concurrent包中的各類各樣的類,例如:CountDownLatch等。使用這些類,不須要本身作同步工做,更方便,也更不容易出錯。
2)若是synchronized關鍵字在程序中適用,就優先實用這個關鍵字。由於這樣能夠減小須要編寫的代碼,就減小了出錯的概率。
3)若是特別須要Lock這樣結構獨有的特性的時候,才使用Lock。
以上三點主要是基於減小代碼出錯爲出發點。
鎖調度機制。對於synchronized內置鎖,不一樣版本的JVM處理方式不一樣,blocked和running都有概率。
(1)優化使用範圍,讓加鎖區在業務容許的狀況下足夠小。
(2)用其餘類型的鎖,例如讀寫鎖,這樣在讀的時候就不止一個線程能夠同時進入代碼。
本身實現一個Lock
在以前的JVM版本中,synchronized性能不是特別的好,而通過不斷的迭代,synchronized性能已經獲得了顯著的提升,這裏面運用的技術就是偏斜鎖、輕量級鎖、重量級鎖。JVM會根據synchronized關鍵字使用到的次數或者其餘的種種指標對鎖進行有效的優化使得性能獲得大幅上漲,這裏面還涉及到了對象頭裏面的字段。