靜態方法加鎖,和非靜態方法加鎖區別


今天看了到有意思的題:在靜態方法上加鎖 和 非靜態方法加鎖 有什么區別,從而再次引出鎖機制的一些理解。

先看方法:

 

// 這是一個很簡單的類,里面共享靜態變量 num,然后一個靜態 和 非靜態方法,都加上鎖

// 我們假設有兩個線程同時操作這兩個方法,那么數據能互斥嗎?

 

Java代碼   收藏代碼
  1. public class Walk {  
  2.     public static int num = 100;  
  3.     public static Walk walk = new Walk();  
  4.     // 靜態  
  5.     public synchronized static   int run(){  
  6.             int i = 0;  
  7.             while (i < 10) {  
  8.                 try {  
  9.                     num --;  
  10.                     i++;  
  11.                     System.out.println(Thread.currentThread().getName()+":"+num);  
  12.                     Thread.sleep(1000);  
  13.                 } catch (InterruptedException e) {  
  14.                     e.printStackTrace();  
  15.                 }  
  16.             }  
  17.             return num ;  
  18.     }  
  19.     // 非靜態  
  20.     public  synchronized int  walk(){  
  21.             int i = 0;  
  22.             while (i < 10) {  
  23.                 try {  
  24.                     num --;  
  25.                     i++;  
  26.                     System.out.println(Thread.currentThread().getName()+":"+num);  
  27.                     Thread.sleep(1000);  
  28.                 } catch (InterruptedException e) {  
  29.                     e.printStackTrace();  
  30.                 }  
  31.             }  
  32.             return num ;  
  33.     }  
  34. }  
  35.   
  36. // 先建立兩個測試類,這里我們默認循環10次  
  37. public class T3 implements Runnable {  
  38.     @Override  
  39.     public void run() {  
  40.         Walk walk = new Walk();  
  41.         //Walk walk = Walk.walk;  
  42.         walk.walk();  
  43.     }  
  44. }  
  45.   
  46. public class T1 implements Runnable{  
  47.     @Override  
  48.     public void run() {  
  49.         Walk walk = new Walk();  
  50.         //Walk walk = Walk.walk;  
  51.         // 這里我依然用的new  
  52.         walk.run();  
  53.     }  
  54. }  

 

Java代碼   收藏代碼
  1. // 測試方法  
  2. public class Test {  
  3.     public static void main(String[] args) {  
  4.         Thread t1 = new  Thread(new T1());  
  5.         Thread t3 = new  Thread(new T3());  
  6.         ExecutorService es = Executors.newCachedThreadPool();  
  7.         es.execute(t1);  
  8.         es.execute(t3);  
  9.         es.shutdown();  
  10.     }  
  11. }  

 // 測試數據 我就不完全列出了

 

pool-1-thread-1:98

pool-1-thread-2:98

pool-1-thread-2:97

pool-1-thread-1:96

.....

可以看出兩個線程沒有互斥,這是為什么呢?

OK,我們將static 關鍵字去掉,代碼我就不貼了,直接看結果。。

pool-1-thread-1:98

pool-1-thread-2:98

pool-1-thread-2:96

... 

結果還是沒有出現互斥現象,因此我們默認要先讓一個線程執行10次的,假設我們這個是買票系統這是不允許的。為什么會出現這狀況呢,方法都加上的鎖的。

 

這里先引一下鎖的理解,然后從后向前解釋。

JAVA 的鎖機制說明:每個對象都有一個鎖,並且是唯一的。假設分配的一個對象空間,里面有多個方法,相當於空間里面有多個小房間,如果我們把所有的小房間都加鎖,因為這個對象只有一把鑰匙,因此同一時間只能有一個人打開一個小房間,然后用完了還回去,再由JVM 去分配下一個獲得鑰匙的人。

 

第二次實驗,我們是對方法進行加鎖了,但是沒得到想要的結果,原因在於房間與鑰匙。因為我們每個線程在調用方法的時候都是new 一個對象,那么就會出現兩個空間,兩把鑰匙,而靜態變量只有一個,相當於我們有兩把鑰匙,從不同的房間開門取共享的值,因此出錯。

 

如果我們使用靜態變量walk 呢?這代碼放開,也就是我們統一使用一個對象去操作變量,那么結果..

 

使用 Walk.walk.walk();  和 Walk.run();

 

結果:還是沒有互斥

pool-1-thread-1:99

pool-1-thread-2:98

pool-1-thread-1:97

...

 

如果我們把靜態方法關鍵字 去掉: 就可以看見互斥現象了

 

pool-1-thread-1:99

pool-1-thread-1:98

pool-1-thread-1:96

 

結果發現還是會重復,因此我們可以得出,在靜態方法上加鎖,和普通方法上加鎖,他們用的不是同一把所,不是同一把鑰匙。從而得出 他們的對象鎖是不同的,對象也是不同的。

 

這里再次引出一個概念:對象鎖  和  類鎖

 

對象鎖:JVM 在創建對象的時候,默認會給每個對象一把唯一的對象鎖,一把鑰匙

類鎖:每一個類都是一個對象,每個對象都擁有一個對象鎖。

 

呵呵,概念感覺混淆了,其實都是鎖,取兩個名詞,下面區分方便,效果是一樣的,如果我們這樣實現。

 

 

Java代碼   收藏代碼
  1. // 靜態,這里僅僅將方法所 變成了 類鎖。  
  2.     public  static int run(){  
  3.         synchronized(Walk.class) {  
  4.             int i = 0;  
  5.             while (i < 10) {  
  6.                 try {  
  7.                     num --;  
  8.                     i++;  
  9.                     System.out.println(Thread.currentThread().getName()+":"+num);  
  10.                     Thread.sleep(1000);  
  11.                 } catch (InterruptedException e) {  
  12.                     e.printStackTrace();  
  13.                 }  
  14.             }  
  15.             return num ;  
  16.         }  
  17.     }  

 

 

結果:

pool-1-thread-1:98

pool-1-thread-2:98

pool-1-thread-2:97

pool-1-thread-1:97

...

發現結果還是不是互斥的,說明在靜態方法上加鎖,和 實例方法加鎖,對象鎖 其實不一樣的。如果我們改成:

synchronized(walk) {

//....略

}

 

結果:

pool-1-thread-2:99

pool-1-thread-2:98

pool-1-thread-2:97

這樣就互斥了,因為T1 是通過靜態變量walk 調用的,默認就是用的walk 對象這把鎖,而靜態方法 強制讓他也使用 walk這把鎖,就出現了互斥現象,因為鑰匙只有一把。

 

如果我們兩個方法都是靜態方法呢?

..

小結:

    1.對象鎖鑰匙只能有一把才能互斥,才能保證共享變量的唯一性

    2.在靜態方法上的鎖,和 實例方法上的鎖,默認不是同樣的,如果同步需要制定兩把鎖一樣。

    3.關於同一個類的方法上的鎖,來自於調用該方法的對象,如果調用該方法的對象是相同的,那么鎖必然相同,否則就不相同。比如 new A().x() 和 new A().x(),對象不同,鎖不同,如果A的單利的,就能互斥。

    4.靜態方法加鎖,能和所有其他靜態方法加鎖的 進行互斥

    5.靜態方法加鎖,和xx.class 鎖效果一樣,直接屬於類的

  6.(自己補的)照上邊所說,如果同一個對象上的2個非static的方法上加鎖,這2個方法雖然不是一個方法,但如果都加鎖的話也會互斥,即同一個對象不同非static的方法加鎖的話一個方法已經拿到鎖了那另外一個線程用同一個對象調用另外一個線程時也會處於等待---總結就是如果鎖非static的方法的話就如同鎖對象,而且同一個對象只有一把鎖。那鎖不同的屬性呢?

 

 

補:

 

 

Java 對象的鎖

分類: Java開發
    Java 中每一個對象都有一個鎖,當線程訪問synchronized 的方法和代碼塊的時候,會獲得這個鎖,也可以理解為為這個對象加鎖,這個鎖在同一時間有且只能被一個線程訪問。如果一個線程訪問對象鎖的時候,鎖正在被其他線程訪問,那么這個線程就需要等待占用鎖的那個線程釋放對象鎖,陷入阻塞狀態。當執行完synchronized方法或者代碼塊的時候,線程就會釋放對象鎖。     當線程通過 synchronized 方法或者代碼塊獲得對象鎖后,其他線程無法訪問對象的任何synchronized方法或者代碼塊,但是可以訪問非synchronized方法或者代碼塊。    如果對靜態方法添加synchronized 或者對靜態變量的修改添加synchronized 塊,那么這個時候獲得的是類對象的鎖。(class對象)    當線程休眠的時候,是不會釋放對象鎖。    可以把對象鎖理解為資源,線程使用完這個資源后,會釋放這個資源,以便其他線程來使用。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM