關於java的Synchronized,你可能需要知道這些(上)


對於使用java同學,synchronized是再熟悉不過了。synchronized是實現線程同步的基本手段,然而底層實現還是通過鎖機制來保證,對於被synchronized修飾的區域每次只有一個線程可以訪問,從而滿足線程安全的目的。那么今天就讓我們聊一聊synchronized的那些事
1.基本用法
   java內存模型(JMM)圍繞原子性,可見性、有序性以及Happen-before原則展開(參考http://www.cnblogs.com/hujunzheng/p/5118256.html),synchronized通過鎖機制的實現,滿足了原子性,可見性和有序性,是並發編程正確執行的有效保障,而volatile只保證了可見性和有序性(禁止指令重排)。
  synchronized可以修飾范圍的包括:方法級別,代碼塊級別;而實際加鎖的目標包括:對象鎖(普通變量,靜態變量),類鎖。
  下面是synchronized的幾種常用方法:
復制代碼
public class SynMethod {
    private static final Object staticLockObj = new Object();
    /**
     * 對象鎖,代碼級別,同一對象爭用該鎖,this為SynMethod實例,synchronized的鎖綁定在this對象上
     */
    public void method1() {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
            }
        }
    }
    
    /**
     * 對象鎖,方法級別,同一對象爭用該鎖,普通(非靜態)方法,synchronized的鎖綁定在調用該方法的對象上,與上一個寫法含義一致
     */
    public synchronized void method2() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
        }
    }

    /**
     * 對象鎖,代碼級別,同一類爭用該鎖,綁定在staticLockObj上,不同SynMethod實例,擁有同一個staticLockObj對象
     */
    public void method3() {
        synchronized (staticLockObj) {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
            }
        }
    }

    /**
     * 類鎖,代碼級別,同一類爭用該鎖
     */
    public void method4() {
        synchronized (SynMethod.class) {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
            }
        }
    }

    /**
     * 類鎖,方法級別,同一類爭用該鎖,synchronized的鎖綁定在SynMethod.class上
     */
    public static synchronized void staticMethod() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
        }
    }
}
復制代碼

  下面我們來測試一下(由於同步運行結果收到線程調度等各種影響,對於無法達到同步效果的情況,需要多運行幾次)

   測試情況1

復制代碼
public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method1();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method1();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}
復制代碼

運行結果:

復制代碼
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4
復制代碼

兩個線程運行了同一個對象t1的同一個public方法method1,這個方法在t1對象上同步,所以實現了同步的效果

  測試情況2

復制代碼
public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method1();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method2();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}
復制代碼

運行結果:

復制代碼
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4
復制代碼

兩個線程運行同一個對象t1的不同的方法method1和method2方法,但是這兩個方法是使用同一個對象t1上進行同步的,所以實現同步的效果,側面印證了這兩種寫法的一致性。

  測試情況3:

復制代碼
public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        final SynMethod t2 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method3();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                t2.method3();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}
復制代碼

運行結果:

復制代碼
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4
復制代碼

這次兩個線程運行了不同的類對象t1和t2的同一個方法method3,這個方法是在一個靜態對象上同步,這個靜態變量是在這個類的所有實例上共享的,所以也是達到了同步的效果

  測試情況4:

復制代碼
public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        final SynMethod t2 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method2();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method3();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}
復制代碼

運行結果:

復制代碼
A synchronized loop 0
B synchronized loop 0
A synchronized loop 1
B synchronized loop 1
A synchronized loop 2
B synchronized loop 2
B synchronized loop 3
A synchronized loop 3
B synchronized loop 4
A synchronized loop 4
復制代碼

這次是兩個線程運行了同一個對象t1的method2和method3方法,這個方法分別在t1對象和SynMethod類的靜態對象上同步,所以達到同步效果

  測試情況5:

復制代碼
public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        final SynMethod t2 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method4();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                t2.method4();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}
復制代碼

運行結果:

復制代碼
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4
復制代碼

兩個線程運行了不同對象t1和t2的同一個方法method4,該方法是在SynMethod類上同步,實現了同步效果

  測試情況6:

復制代碼
public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        final SynMethod t2 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method4();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                SynMethod.staticMethod();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}
復制代碼

運行結果:

復制代碼
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4
復制代碼

兩個線程分別運行了對象t1的method4和靜態方法staticMethod,這個兩個方法都在SynMethod類上同步,實現了同步的效果。

  測試情況7:

復制代碼
public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        final SynMethod t2 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method4();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                t2.method3();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}
復制代碼

運行結果:

復制代碼
A synchronized loop 0
B synchronized loop 0
A synchronized loop 1
B synchronized loop 1
A synchronized loop 2
B synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 3
B synchronized loop 4
復制代碼

這次兩個線程運行了兩個對象的method3和method4發放,這個兩個方法分別在SynMethod類和SynMethod類的靜態對象上同步,所以沒有達到同步效果

  測試情況8:

復制代碼
public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        final SynMethod t2 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method4();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                t2.method2();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}
復制代碼

運行結果:

復制代碼
A synchronized loop 0
B synchronized loop 0
A synchronized loop 1
B synchronized loop 1
A synchronized loop 2
B synchronized loop 2
A synchronized loop 3
B synchronized loop 3
A synchronized loop 4
B synchronized loop 4
復制代碼

這次兩個線程運行了兩個對象的method4和method2方法,這兩個方法分別在SynMethod類和對象t2上同步,所以沒有達到同步效果。

  使用總結:雖然上面說的情況比較多,但是從同步對象的角度,同步的場景只用三個,一個是SynMethod實例(可以多個),SynMethod的靜態對象(共享)和SynMethod類(一個),只要是在同一個對象上同步,這個對象可以是實例對象,可以是靜態對象,可以是類對象,那么就可以實現同步效果,否則無法達到同步,這也與synchronized設計的初衷一致。

  

2.實現原理

  首先我們將SynMethod編譯一下(命令:javac SynMethod.java),得到.class文件SynMethod.class,再通過反編譯命令(javap -c SynMethod.class)

 

復制代碼
Compiled from "SynMethod.java"
public class concurrent.SynMethod {
  public concurrent.SynMethod();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public void method1();
    Code:
       0: aload_0       
       1: dup           
       2: astore_1      
       3: monitorenter  
       4: iconst_0      
       5: istore_2      
       6: iload_2       
       7: iconst_5      
       8: if_icmpge     51
      11: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      14: new           #3                  // class java/lang/StringBuilder
      17: dup           
      18: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      21: invokestatic  #5                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      24: invokevirtual #6                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      27: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      30: ldc           #8                  // String  synchronized loop 
      32: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      35: iload_2       
      36: invokevirtual #9                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      39: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      42: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      45: iinc          2, 1
      48: goto          6
      51: aload_1       
      52: monitorexit   
      53: goto          61
      56: astore_3      
      57: aload_1       
      58: monitorexit   
      59: aload_3       
      60: athrow        
      61: return        
    Exception table:
       from    to  target type
           4    53    56   any
          56    59    56   any

  public synchronized void method2();
    Code:
       0: iconst_0      
       1: istore_1      
       2: iload_1       
       3: iconst_5      
       4: if_icmpge     47
       7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: new           #3                  // class java/lang/StringBuilder
      13: dup           
      14: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      17: invokestatic  #5                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      20: invokevirtual #6                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      23: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      26: ldc           #8                  // String  synchronized loop 
      28: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      31: iload_1       
      32: invokevirtual #9                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      35: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      38: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      41: iinc          1, 1
      44: goto          2
      47: return        

  public void method3();
    Code:
       0: getstatic     #12                 // Field staticLockObj:Ljava/lang/Object;
       3: dup           
       4: astore_1      
       5: monitorenter  
       6: iconst_0      
       7: istore_2      
       8: iload_2       
       9: iconst_5      
      10: if_icmpge     53
      13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      16: new           #3                  // class java/lang/StringBuilder
      19: dup           
      20: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      23: invokestatic  #5                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      26: invokevirtual #6                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      29: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      32: ldc           #8                  // String  synchronized loop 
      34: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      37: iload_2       
      38: invokevirtual #9                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      41: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      44: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      47: iinc          2, 1
      50: goto          8
      53: aload_1       
      54: monitorexit   
      55: goto          63
      58: astore_3      
      59: aload_1       
      60: monitorexit   
      61: aload_3       
      62: athrow        
      63: return        
    Exception table:
       from    to  target type
           6    55    58   any
          58    61    58   any

  public void method4();
    Code:
       0: ldc_w         #13                 // class concurrent/SynMethod
       3: dup           
       4: astore_1      
       5: monitorenter  
       6: iconst_0      
       7: istore_2      
       8: iload_2       
       9: iconst_5      
      10: if_icmpge     53
      13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      16: new           #3                  // class java/lang/StringBuilder
      19: dup           
      20: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      23: invokestatic  #5                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      26: invokevirtual #6                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      29: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      32: ldc           #8                  // String  synchronized loop 
      34: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      37: iload_2       
      38: invokevirtual #9                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      41: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      44: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      47: iinc          2, 1
      50: goto          8
      53: aload_1       
      54: monitorexit   
      55: goto          63
      58: astore_3      
      59: aload_1       
      60: monitorexit   
      61: aload_3       
      62: athrow        
      63: return        
    Exception table:
       from    to  target type
           6    55    58   any
          58    61    58   any

  public static synchronized void staticMethod();
    Code:
       0: iconst_0      
       1: istore_0      
       2: iload_0       
       3: iconst_5      
       4: if_icmpge     47
       7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: new           #3                  // class java/lang/StringBuilder
      13: dup           
      14: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      17: invokestatic  #5                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      20: invokevirtual #6                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      23: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      26: ldc           #8                  // String  synchronized loop 
      28: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      31: iload_0       
      32: invokevirtual #9                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      35: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      38: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      41: iinc          0, 1
      44: goto          2
      47: return        

  static {};
    Code:
       0: new           #14                 // class java/lang/Object
       3: dup           
       4: invokespecial #1                  // Method java/lang/Object."<init>":()V
       7: putstatic     #12                 // Field staticLockObj:Ljava/lang/Object;
      10: return        
}
復制代碼

  通過反編譯出來的代碼可以看到,方法級別的synchronized同步使用了monitorenter和monitorexit這個同步命令,而monitorexit出現了兩次,猜測是由於異常處理的需要

  monitorenter和monitorexit這兩個命令的解釋參考JVM規范:

  monitorenter :

  Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

  這段話的大概意思為:

  每個對象有一個監視器鎖(monitor)。當monitor被占用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權,過程如下:

1、如果monitor的進入數為0,則該線程進入monitor,然后將進入數設置為1,該線程即為monitor的所有者。

2、如果線程已經占有該monitor,只是重新進入,則進入monitor的進入數加1.

3.如果其他線程已經占用了monitor,則該線程進入阻塞狀態,直到monitor的進入數為0,再重新嘗試獲取monitor的所有權。

 

  monitorexit: 

  The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

  這段話的大概意思為:

  執行monitorexit的線程必須是objectref所對應的monitor的所有者。指令執行時,monitor的進入數減1,如果減1后進入數為0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去獲取這個 monitor 的所有權。 

  通過這兩段描述,我們應該能很清楚的看出Synchronized的實現原理,Synchronized的語義底層是通過一個monitor的對象來完成,其實wait/notify等方法也依賴於monitor對象,這就是為什么只有在同步的塊或者方法中才能調用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因。
 
  對於方法級別的同步,在flag標志中多了ACC_SYNCHRONIZED標示符,用於標記整個方法的同步,JVM在執行該方法前會讀取該標記符,從而調用monitorentor命令,在退出方法時調用monitorexit命令,從而達到同步的效果(這里反編譯的結果並沒有看到flag字段,可以參考http://www.cnblogs.com/paddix/p/5367116.html)
  
   JSR 133(Java Memory Model)FAQ(http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html)中關於synchronized的問題中提到,synchronized除了保證線程對該代碼塊的互斥訪問外,還有下面這么一段話
  But there is more to synchronization than mutual exclusion. Synchronization ensures that memory writes by a thread before or during a synchronized block are made visible in a predictable manner to other threads which synchronize on the same monitor. After we exit a synchronized block, we  release the monitor, which has the effect of flushing the cache to main memory, so that writes made by this thread can be visible to other threads. Before we can enter a synchronized block, we  acquire the monitor, which has the effect of invalidating the local processor cache so that variables will be reloaded from main memory. We will then be able to see all of the writes made visible by the previous release.
  大概的意思是,同步不僅僅保證互斥訪問,同步還保證當前線程在同步塊前和同步塊中,對內存的寫操作對於其他訪問相同同步塊(使用了同一個monitor)的線程是可見的。當我們退出了同步塊,會釋放monitor,並且將緩存數據刷新到內存,這樣當前線程的寫操作對於其他線程是可見的,當我們進入同步快之前,會獲取monitor,並且使得當前處理器的緩存失效,從而讀取數據必須從內存中重新加載,這樣我們就可以看到其他線程在同步塊中寫操作
 
3.總結
  Synchronized是java常用的同步手段,正確合理的使用是編寫好並發程序的關鍵,本文主要是從使用和實現原理的角度來說,下個文章將會講講關於synchronized的底層鎖優化以,偏向鎖哪些事
 
參考:
http://www.cnblogs.com/paddix/p/5367116.html
https://blog.csdn.net/sum_rain/article/details/39892219
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html

 


免責聲明!

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



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