Synchronized是Java中解決併發問題的一種最經常使用的方法,也是最簡單的一種方法。Synchronized的做用主要有三個:(1)確保線程互斥的訪問同步代碼(2)保證共享變量的修改可以及時可見(3)有效解決重排序問題。從語法上講,Synchronized總共有三種用法:html
1)、沒有同步的狀況:java
/** * 沒有同步的狀況 * * Created by Jiacheng on 2018/6/28. */ public class SynchronizedTest { public void method1() { System.out.println("Method 1 start"); try { System.out.println("Method 1 execute"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 1 end"); } public void method2() { System.out.println("Method 2 start"); try { System.out.println("Method 2 execute"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 2 end"); } public static void main(String[] args) { final SynchronizedTest test = new SynchronizedTest(); new Thread(() -> test.method1()).start(); new Thread(() -> test.method2()).start(); } }
2)、對普通方法同步:編程
/** * 同步實例方法 * * Created by Jiacheng on 2018/6/28. */ public class SynchronizedMethod { public synchronized void method1() { System.out.println("Method 1 start"); try { System.out.println("Method 1 execute"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 1 end"); } public synchronized void method2() { System.out.println("Method 2 start"); try { System.out.println("Method 2 execute"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 2 end"); } public static void main(String[] args) { final SynchronizedMethod test = new SynchronizedMethod(); new Thread(() -> test.method1()).start(); new Thread(() -> test.method2()).start(); } }
雖然method1和method2是不一樣的方法,可是這兩個方法都進行了同步,而且是經過同一個對象去調用的,因此調用以前都須要先去競爭同一個對象上的鎖(monitor),也就只能互斥的獲取到鎖,所以,method1和method2只能順序的執行。多線程
3)、靜態方法(類)同步:併發
/** * 同步靜態方法 * * Created by Jiacheng on 2018/6/28. */ public class SynchronizedStatic { public static synchronized void method1() { System.out.println("Method 1 start"); try { System.out.println("Method 1 execute"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 1 end"); } public static synchronized void method2() { System.out.println("Method 2 start"); try { System.out.println("Method 2 execute"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 2 end"); } public static void main(String[] args) { final SynchronizedStatic test = new SynchronizedStatic(); final SynchronizedStatic test2 = new SynchronizedStatic(); new Thread(() -> test.method1()).start(); new Thread(() -> test2.method2()).start(); } }
雖然test和test2屬於不一樣對象,可是test和test2屬於同一個類的不一樣實例,因爲method1和method2都屬於靜態同步方法,對靜態方法的同步本質上是對類的同步(靜態方法本質上是屬於類的方法,而不是對象上的方法),因此調用的時候須要獲取同一個類上monitor(每一個類只對應一個class對象),因此也只能順序的執行。函數
4)、代碼塊同步:高併發
/** * 同步方法塊 * * Created by Jiacheng on 2018/6/28. */ public class SynchronizedBlock { public void method1() { System.out.println("Method 1 start"); try { synchronized (this) { System.out.println("Method 1 execute"); Thread.sleep(3000); } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 1 end"); } public void method2() { System.out.println("Method 2 start"); try { synchronized (this) { System.out.println("Method 2 execute"); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 2 end"); } public static void main(String[] args) { final SynchronizedBlock test = new SynchronizedBlock(); new Thread(() -> test.method1()).start(); new Thread(() -> test.method2()).start(); } }
對於代碼塊的同步實質上須要獲取Synchronized關鍵字後面括號中對象的monitor,因爲這段代碼中括號的內容都是this,而method1和method2又是經過同一的對象去調用的,因此進入同步塊以前須要去競爭同一個對象上的鎖,所以只能順序執行同步塊。性能
每一個對象有一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的全部權,過程以下:this
1)若是monitor的進入數爲0,則該線程進入monitor,而後將進入數設置爲1,該線程即爲monitor的全部者。spa
2)若是線程已經佔有該monitor,只是從新進入,則進入monitor的進入數加1.
3)若是其餘線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數爲0,再從新嘗試獲取monitor的全部權。
synchronized 方法控制對類成員變量的訪問:每一個類實例對應一把鎖,每一個 synchronized 方法都必須得到調用該方法的類實例的鎖方能執行,不然所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能得到該鎖,從新進入可執行狀態。這種機制確保了同一時刻對於每個類實例,其全部聲明爲 synchronized 的成員函數中至多隻有一個處於可執行狀態(由於至多隻有一個可以得到該類實例對應的鎖),從而有效避免了類成員變量的訪問衝突(只要全部可能訪問類成員變量的方法均被聲明爲 synchronized)。
在 Java 中,不光是類實例,每個類也對應一把鎖,這樣咱們也可將類的靜態成員函數聲明爲 synchronized ,以控制其對類的靜態成員變量的訪問。synchronized方法實際上等同於用一個synchronized塊包住方法中的全部語句,而後在synchronized塊的括號中傳入this關鍵字。固然若是是靜態方法,須要鎖定的則是class對象。
可能一個方法中只有幾行代碼涉及到線程同步的問題,因此synchronized塊比synchronized方法更近細粒度的控制了多個線程的訪問,只有synchronized塊中的內容不能同時被多個線程訪問,方法中的其餘語句仍然能夠同時被多個線程所訪問(包括synchronized塊以前和以後的)。
使用synchronized,當多個線程嘗試獲取鎖時,未獲取到鎖的線程會不斷的嘗試獲取鎖,而不會發生中斷,這樣會形成性能消耗。
參考資料
《Java多線程編程實戰指南》