java併發編程這個領域中synchronized關鍵字一直都是元老級的角色,在java早期版本中,synchronized屬於重量級鎖,效率低下,由於監視器鎖(monitor)是依賴於底層的操做系統的Mutex Lock來實現的,java的線程是映射到操做系統的原生線程之上的。若是要掛起或者喚醒一個線程,都須要操做系統幫忙完成,而操做系統實現線程之間的切換時須要從用戶態轉換到內核態,這個狀態之間的轉換須要相對比較長的時間,時間成本相對較高。在JDK1.6以後java官方對從JVM層面對synchronized 較大優化,因此如今的synchronized鎖效率也優化得很不錯。JDK1.6對鎖的實現引入了大量的優化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減小鎖操做的開銷。java
經過反編譯下面的代碼來看看Synchronized是如何實現對代碼塊進行同步的,切換到類的對應目錄執行javac SynchronizedTest.java命令生成編譯後的.class文件,而後執行javap -v SynchronizedTest.class。編程
1)synchronized同步代碼塊併發
public class SynchronizedTest { public static void main(String[] args) { new SynchronizedTest().method(); } public void method() { synchronized (this) { System.out.println("synchronized 代碼塊"); } } }
從上面咱們能夠看出:synchronized 同步語句塊的實現使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結束位置。 當執行 monitorenter 指令時,線程試圖獲取鎖也就是獲取 monitor(monitor對象存在於每一個Java對象的對象頭中,synchronized 鎖即是經過這種方式獲取鎖的,也是爲何Java中任意對象能夠做爲鎖的緣由) 的持有權.當計數器爲0則能夠成功獲取,獲取後將鎖計數器設爲1也就是加1。相應的在執行 monitorexit 指令後,將鎖計數器設爲0,代表鎖被釋放。若是獲取對象鎖失敗,那當前線程就要阻塞等待,直到鎖被另一個線程釋放爲止。異步
synchronized的語義底層是經過一個monitor的對象來完成,其實wait/notify等方法也依賴於monitor對象,這就是爲何只有在同步的塊或者方法中才能調用wait/notify等方法,不然會拋出java.lang.IllegalMonitorStateException的異常的緣由。ide
2)synchronized修飾方法優化
public class SynchronizedTest { public static void main(String[] args) { new SynchronizedTest().method(); } public synchronized void method() { System.out.println("Hello World!"); } }
synchronized 修飾的方法的同步並無 monitorenter 指令和 monitorexit 指令完成(理論上其實也能夠經過這兩條指令來實現),不過相對於普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。JVM就是根據該標示符來實現方法的同步的:當方法調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,若是設置了,執行線程將先獲取monitor,獲取成功以後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其餘任何線程都沒法再得到同一個monitor對象。 其實本質上沒有區別,只是方法的同步是一種隱式的方式來實現,無需經過字節碼來完成。this
小結:對於同步塊的實現使用了monitorenter和monitorexit指令,而同步方法則是依靠方法修飾符上的ACC_SYNCHRONIZED來完成的。不管採用哪一種方法,其本質是對一個對象的監視器(monitor)進行獲取,而這個獲取過程是排他的,也就是同一個時刻只能有一個線程獲取到由synchronized所保護對象的監視器。spa
synchronized實現同步的基礎是:Java中每一個對象均可以做爲鎖,具體表現爲如下三種形式:
1.對於普通同步方法,鎖是當前實例對象;
2.對於靜態同步方法,鎖是當前類的class對象;
3.對於同步方法塊,鎖是synchronized括號裏配置的對象操作系統
1)多個線程訪問的是多個對象.net
public class HasSelfPrivateNum { private int num = 0; public static void main(String[] args) { HasSelfPrivateNum numRef1 = new HasSelfPrivateNum(); HasSelfPrivateNum numRef2 = new HasSelfPrivateNum(); ThreadTestA thread1 = new ThreadTestA(numRef1); thread1.start(); ThreadTestB thread2 = new ThreadTestB(numRef2); thread2.start(); } synchronized public void add(String username) { try { if (username.equals("a")) { num = 100; System.out.println("a set over!"); Thread.sleep(2000); } else { num = 200; System.out.println("b set over!"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { e.printStackTrace(); } } static public class ThreadTestA extends Thread { private HasSelfPrivateNum numRef; public ThreadTestA(HasSelfPrivateNum numRef) { this.numRef = numRef; } @Override public void run() { numRef.add("a"); } } static public class ThreadTestB extends Thread { private HasSelfPrivateNum numRef; public ThreadTestB(HasSelfPrivateNum numRef) { this.numRef = numRef; } @Override public void run() { numRef.add("b"); } } }
兩個線程ThreadTestA和ThreadTestB分別訪問同一個類的不一樣實例的相同名稱的同步方法,可是效果確實異步執行,由於synchronized取得的鎖都是對象鎖,而不是把一段代碼或方法當作鎖。因此在上面的實例中,哪一個線程先執行帶synchronized關鍵字的方法,則哪一個線程就持有該方法所屬對象的鎖Lock,那麼其餘線程只能呈等待狀態。當前建立了兩個HasSelfPrivateNum類對象,因此就產生了兩個鎖。當ThreadTestA的引用執行到add方法run中的Thread.sleep(2000)語句時,ThreadB就會「伺機執行」。
2)多個線程訪問的是同一個對象
public static void main(String[] args) { HasSelfPrivateNum numRef = new HasSelfPrivateNum(); ThreadTestA thread1 = new ThreadTestA(numRef); thread1.start(); ThreadTestB thread2 = new ThreadTestB(numRef); thread2.start(); }
多個線程訪問的是同一個對象,哪一個線程先執行帶synchronized關鍵字的方法,則哪一個線程就持有該方法,那麼其餘線程只能呈等待狀態。若是多個線程訪問的是多個對象則不必定,由於多個對象會產生多個鎖。
3)髒讀
發生髒讀的狀況實在讀取實例變量時,此值已經被其餘線程更改過。
public class PublicVar { public String username = "A"; public String password = "AA"; synchronized public void setValue(String username, String password) { try { this.username = username; Thread.sleep(3000); this.password = password; System.out.println("setValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password); } catch (InterruptedException e) { e.printStackTrace(); } } //該方法前加上synchronized關鍵字就同步了 public void getValue() { System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password); } public static void main(String[] args) { try { PublicVar publicVarRef = new PublicVar(); ThreadC thread = new ThreadC(publicVarRef); thread.start(); Thread.sleep(500);//打印結果受此值大小影響 publicVarRef.getValue(); } catch (InterruptedException e) { e.printStackTrace(); } } static class ThreadC extends Thread { private PublicVar publicVar; public ThreadC(PublicVar publicVar) { this.publicVar = publicVar; } @Override public void run() { publicVar.setValue("B", "BB"); } } }
4)靜態同步synchronized方法與synchronized(class)代碼塊
synchronized關鍵字加到static靜態方法和synchronized(class)代碼塊上都是是給Class類上鎖,而synchronized關鍵字加到非static靜態方法上是給對象上鎖。
public class SynchronizedTest2 { public static void printA() { synchronized (SynchronizedTest2.class) { try { System.out.println( "線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printA"); Thread.sleep(3000); System.out.println( "線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printA"); } catch (InterruptedException e) { e.printStackTrace(); } } } synchronized public static void printB() { System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printB"); System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printB"); } synchronized public void printC() { System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printC"); System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printC"); } static class ThreadA extends Thread { private SynchronizedTest2 test; public ThreadA(SynchronizedTest2 test) { this.test = test; } @Override public void run() { test.printA(); } } static class ThreadB extends Thread { private SynchronizedTest2 test; public ThreadB(SynchronizedTest2 test) { this.test = test; } @Override public void run() { test.printB(); } } static class ThreadC extends Thread { private SynchronizedTest2 test; public ThreadC(SynchronizedTest2 test) { this.test = test; } @Override public void run() { test.printC(); } } public static void main(String[] args) { SynchronizedTest2 test = new SynchronizedTest2(); ThreadA a = new ThreadA(test); a.setName("A"); a.start(); ThreadB b = new ThreadB(test); b.setName("B"); b.start(); ThreadC c = new ThreadC(test); c.setName("C"); c.start(); } }
靜態同步synchronized方法與synchronized(class)代碼塊持有的鎖同樣,都是Class鎖,Class鎖對對象的全部實例起做用。synchronized關鍵字加到非static靜態方法上持有的是對象鎖。線程A,B和線程C持有的鎖不同,因此A和B運行同步,可是和C運行不一樣步。