今天看了到有意思的題:在靜態方法上加鎖 和 非靜態方法加鎖 有什么區別,從而再次引出鎖機制的一些理解。
先看方法:
// 這是一個很簡單的類,里面共享靜態變量 num,然后一個靜態 和 非靜態方法,都加上鎖
// 我們假設有兩個線程同時操作這兩個方法,那么數據能互斥嗎?
- public class Walk {
- public static int num = 100;
- public static Walk walk = new Walk();
- // 靜態
- public synchronized static int run(){
- int i = 0;
- while (i < 10) {
- try {
- num --;
- i++;
- System.out.println(Thread.currentThread().getName()+":"+num);
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- return num ;
- }
- // 非靜態
- public synchronized int walk(){
- int i = 0;
- while (i < 10) {
- try {
- num --;
- i++;
- System.out.println(Thread.currentThread().getName()+":"+num);
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- return num ;
- }
- }
- // 先建立兩個測試類,這里我們默認循環10次
- public class T3 implements Runnable {
- @Override
- public void run() {
- Walk walk = new Walk();
- //Walk walk = Walk.walk;
- walk.walk();
- }
- }
- public class T1 implements Runnable{
- @Override
- public void run() {
- Walk walk = new Walk();
- //Walk walk = Walk.walk;
- // 這里我依然用的new
- walk.run();
- }
- }
- // 測試方法
- public class Test {
- public static void main(String[] args) {
- Thread t1 = new Thread(new T1());
- Thread t3 = new Thread(new T3());
- ExecutorService es = Executors.newCachedThreadPool();
- es.execute(t1);
- es.execute(t3);
- es.shutdown();
- }
- }
// 測試數據 我就不完全列出了
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 在創建對象的時候,默認會給每個對象一把唯一的對象鎖,一把鑰匙
類鎖:每一個類都是一個對象,每個對象都擁有一個對象鎖。
呵呵,概念感覺混淆了,其實都是鎖,取兩個名詞,下面區分方便,效果是一樣的,如果我們這樣實現。
- // 靜態,這里僅僅將方法所 變成了 類鎖。
- public static int run(){
- synchronized(Walk.class) {
- int i = 0;
- while (i < 10) {
- try {
- num --;
- i++;
- System.out.println(Thread.currentThread().getName()+":"+num);
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- return num ;
- }
- }
結果:
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的方法的話就如同鎖對象,而且同一個對象只有一把鎖。那鎖不同的屬性呢?
補: