public class AccountingSync implements Runnable{
//共享資源(臨界資源)
static int i=0;
/** * synchronized 修飾實例方法 * 暫時先不添加 */
public void increase(){
i++;
}
@Override
public void run() {
for(int j=0;j<1000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
AccountingSync instance=new AccountingSync();
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
/** * 輸出結果: * 1802452 */
}
複製代碼
i++ 賦值操做並無 原子性 在讀取原來的參數和返回新的參數的這段時間中,若是咱們第二個線程也作了一樣的操做,兩個線程看到的參數是同樣的,一樣執行了 +1 的操做,這裏就形成了線程安全破壞,咱們最終輸出的結果是 1802452java
若是咱們添加上 synchronized 此時 increase() 方法在一個時間內 只能被一個線程讀寫,也就避免髒數據的產生。編程
可是這樣也不是安全的,若是咱們 new 出兩個 AccountingSync 對象去執行操做 結果是怎麼樣?安全
public class AccountingSyncBad implements Runnable{
static int i=0;
public synchronized void increase(){
i++;
}
@Override
public void run() {
for(int j=0;j<1000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
//new新實例
Thread t1=new Thread(new AccountingSyncBad());
//new新實例
Thread t2=new Thread(new AccountingSyncBad());
t1.start();
t2.start();
//join含義:當前線程A等待thread線程終止以後才能從thread.join()返回
t1.join();
t2.join();
System.out.println(i);
}
}
複製代碼
結果依舊是產生了髒數據,緣由是兩個實例對象鎖並不一樣相同,此時若是兩個線程操做數據並不是共享的,線程安全是有保障的,遺憾的是若是兩個線程操做的是共享數據,安全將沒有保障,他們自己的鎖只能保持自己數據結構
public class AccountingSyncClass implements Runnable{
static int i=0;
/** * 做用於靜態方法,鎖是當前class對象,也就是 * AccountingSyncClass類對應的class對象 */
public static synchronized void increase(){
i++;
}
/** * 非靜態,訪問時鎖不同不會發生互斥 */
public synchronized void increase4Obj(){
i++;
}
@Override
public void run() {
for(int j=0;j<1000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
//new新實例
Thread t1=new Thread(new AccountingSyncClass());
//new心事了
Thread t2=new Thread(new AccountingSyncClass());
//啓動線程
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
複製代碼
這個實例當中咱們將 synchronized 做用於靜態方法了,由於靜態方法不屬於任何實例對象,它是類成員,因此這把鎖也能夠理解爲加在了 Class 上 ,可是若是咱們線程A 調用了 class 內部 static synchronized 方法 線程B 調用了 class 內部 非 static 方法 是能夠的,不會發生互斥現象,由於訪問靜態 synchronized 方法佔用的鎖是當前類的class對象,而訪問非靜態 synchronized 方法佔用的鎖是當前實例對象鎖多線程
public class AccountingSync implements Runnable{
static AccountingSync instance=new AccountingSync();
static int i=0;
@Override
public void run() {
//省略其餘耗時操做....
//使用同步代碼塊對變量i進行同步操做,鎖對象爲instance
synchronized(instance){
for(int j=0;j<1000000;j++){
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
複製代碼
內部使用 monitorenter 和 monitorexit 指令實現,monitorenter 指令插入到同步代碼塊的開始位置,monitorexit 指令插入到同步代碼塊的結束位置,jvm須要保證每個monitorenter都有一個 monitorexit 與之對應。任何一個對象都有一個 monitor 與之相關聯,當它的monitor被持有以後,它將處於鎖定狀態。線程執行到 monitorenter 指令前,將會嘗試獲取對象所對應的 monitor 全部權,即嘗試獲取對象的鎖;將線程執行到 monitorexit 時就會釋放鎖。jvm
(人話:)每一個對象都會與一個monitor相關聯,當某個monitor被擁有以後就會被鎖住,當線程執行到monitorenter指令時,就會去嘗試得到對應的monitor。步驟以下:ide
每一個monitor維護着一個記錄着擁有次數的計數器。未被擁有的monitor的計數器爲0,當一個線程得到monitor(執行monitorenter)後,該計數器自增變爲 1 。當同一個線程再次得到該monitor的時候,計數器再次自增;當不一樣線程想要得到該monitor的時候,就會被阻塞。post
當同一個線程釋放monitor(執行monitorexit指令)的時候,計數器再自減。當計數器爲0的時候。monitor將被釋放,其餘線程即可以得到monitor。 spa
不過相對於普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。JVM就是根據該標示符來實現方法的同步的:當方法調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,若是設置了,執行線程將先獲取monitor,獲取成功以後才能執行方法體,方法執行完後再釋放monitor。線程
在jvm字節碼層面並無任何特別的指令來實現synchronized修飾的方法,而是在class文件中將該方法的access_flags字段中的acc_synchronized標誌位設置爲1,表示該方法爲synchronized方法。
在java設計中,每個對象自打孃胎裏出來就帶了一把看不見的鎖,即monitor鎖。monitor是線程私有的數據結構,每個線程都有一個monitor record列表,同時還有一個全局可用列表。每個被鎖住對象都會和一個monitor關聯。monitor中有一個owner字段存放擁有該對象的線程的惟一標識,表示該鎖被這個線程佔有。owner:初始時爲null,表示當前沒有任何線程擁有該monitor,當線程成功擁有該鎖後,owner保存線程惟一標識,當鎖被釋放時,owner又變爲null。
總結: 同步方法和同步代碼塊底層都是經過monitor來實現同步的。二者的區別:同步方式是經過方法中的access_flags中設置ACC_SYNCHRONIZED標誌來實現;同步代碼塊是經過monitorenter和monitorexit來實現咱們知道了每一個對象都與一個monitor相關聯。而monitor能夠被線程擁有或釋放。