一、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,然后試圖進行降級。
