一、synchronized的三種應用方式
1、修飾實例方法,鎖是當前實例對象,進入同步代碼前要獲得當前實例的鎖
/** * synchronized修飾實例方法,當前線程的鎖是實例對象accountingSync * 當一個線程正在訪問一個對象的synchronized實例方法,那么其他線程不能訪問該對象的其他synchronized方法 * 一個對象只有一把鎖 */ public class AccountingSync implements Runnable { static AccountingSync accountingSync = new AccountingSync(); //共享資源 static int i = 0; static int j = 0; public synchronized void increase() { i++; } @Override public void run() { for(int i =0;i<1000000;i++){ synchronized (this){ increase(); } } } public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(accountingSync); Thread thread2 = new Thread(accountingSync); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(i); } }
/** * thread1訪問實例對象obj1的synchronized方法,thread2訪問實例對象obj1的synchronized方法 * 這樣是允許的,因為兩個實例對象鎖並不相同。 * 此時如果兩個線程操作數據非共享,線程安全有保證,如果數據共享,線程安全無法保證 * */ public class AccountingSyncBad implements Runnable { static int i = 0; public synchronized void increase() { i++; } @Override public void run() { for (int i = 0; i < 1000000; i++) { increase(); } } public static void main(String[] args) throws InterruptedException{ //new新實例 Thread thread1 = new Thread(new AccountingSyncBad()); //new新實例 Thread thread2 = new Thread(new AccountingSyncBad()); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(i); } }
2、修飾靜態方法,鎖是當前類的 class對象,進入同步代碼前要獲得當前類對象的鎖
public class AccountingSyncClass implements Runnable{ static int i = 0; /** * synchronized作用於靜態方法,鎖是當前class對象 */ public static synchronized void increase() { i++; } /** * increase4Obj方法是實例方法,其對象鎖是當前實例對象, * 如果別的線程調用該方法,將不會產生互斥現象,畢竟鎖對象不同, * 但我們應該意識到這種情況下可能會發現線程安全問題(操作了共享靜態變量i)。 */ public synchronized void increase4Obj(){ i++; } @Override public void run() { for (int i = 0; i < 1000000; i++) { increase(); // increase4Obj(); } } public static void main(String[] args) throws InterruptedException{ //new新實例 Thread thread1 = new Thread(new AccountingSyncClass()); //new新實例 Thread thread2 = new Thread(new AccountingSyncClass()); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(i); } }
3、修飾代碼塊
synchronized(this) 鎖是當前實例對象,
synchronized(AccountingSync.class) 鎖是class對象
二、synchronized代碼塊底層原理
synchronized代碼塊是由一對monitorenter和monitorexit指令實現的,Monitor對象是同步的基本實現單元。
現代java虛擬機對sychronized進行了優化,引入了偏斜鎖、輕量級鎖、重量級鎖
三、java虛擬機對Synchronized的優化
JVM優化synchronized運行的機制,當JVM檢測到不同的競爭情況時,會自動切換到適合的鎖實現
1、當沒有競爭出現時,默認會使用偏斜鎖。JVM 會利用 CAS操作,在對象頭上的Mark Word部分設置線程ID,以表示這個對象偏向於當前線程,所以並不涉及真正的互斥鎖。這樣做的假設是基於在很多應用場景中,大部分對象生命周期中最多會被一個線程鎖定,使用偏斜鎖可以降低無競爭開銷。
2、有競爭出現時,當有另外的線程試圖鎖定某個已經被偏斜鎖鎖定的對象,jvm就會撤銷revoke偏斜鎖,並切換到輕量級鎖。輕量級鎖依賴CAS操作Mark Word來試圖獲取鎖,如果成功,就使用輕量級鎖,否則繼續升級未重量級鎖
PS:鎖降級也是存在的,當JVM進入SafePoint安全點的時候,會檢查是否有閑置的Monitor,然后試圖進行降級。