【Java】synchronized關鍵字及其實現原理

       Java中關鍵字synchronized修飾方法或者同步塊,它保證多個線程在同一時刻只有一個線程處於被修飾的方法或者同步塊中,保證線程對變量訪問的可見性和排他性。

    synchronized底層是用監視器(monitor)機制實現的。任意一個對象都擁有自己的監視器,當這個對象在同步塊被synchronized修飾或者這個對象的方法被synchronized修飾時,執行該區域代碼的線程必須先獲取到該對象的監視器(Monitor.Enter)才能進入同步塊或者同步方法,而沒有獲取到該對象監視器的線程會被阻塞在同步塊和同步方法的入口處,此時線程進入同步隊列,處於BLOCKED狀態。當獲取對象的監視器的線程釋放了鎖(Monitor.Exit),則該釋放操作喚醒阻塞在同步隊列中的線程,使其重新嘗試對該對象監視器的獲取。

       一、synchronized用法

1、synchronized修飾類的實例(對象)或者非static成員方法

       synchronized修飾類的實例(對象)和修飾非static成員方法的作用效果是一致的:

package com.dxc.test.synchonized;

/**
 * synchronized關鍵字鎖對象測試
 *
 * @author dxc
 * @date 2019/5/21
 */
public class SynchronizedTest {

    /**
     * synchronized修飾非static成員方法
     * */
    public synchronized void test(){
        System.out.println("線程3同步塊開始執行:startTime3:" + System.currentTimeMillis());
    }

    public static void main(String[] args) {
        SynchronizedTest test = new SynchronizedTest();
        new Thread(() -> {
            synchronized (test) {
                System.out.println("線程1同步塊開始執行:startTime1:" + System.currentTimeMillis());
                try {
                    Thread.sleep(10000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("線程1結束執行:endTime1:" + System.currentTimeMillis());
        }).start();
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            synchronized (test) {
                System.out.println("線程2同步塊開始執行:startTime2:" + System.currentTimeMillis());
                try {
                    Thread.sleep(3000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("線程2結束執行:endTime2:" + System.currentTimeMillis());
        }).start();
        new Thread(() -> {
            test.test();
            System.out.println("線程3結束執行:endTime3:" + System.currentTimeMillis());
        }).start();
    }
}

某一次運行結果:

E:\jdk-11.0.2\bin\java.exe "-javaagent:E:\Intellij idea\lib\idea_rt.jar=65395:E:\Intellij idea\bin" -Dfile.encoding=UTF-8 -classpath F:\OpenTalk\blog\target\classes com.dxc.test.synchonized.SynchronizedTest
線程1同步塊開始執行:startTime1:1558452921970
線程1結束執行:endTime1:1558452932008
線程2同步塊開始執行:startTime2:1558452932008
線程3同步塊開始執行:startTime3:1558452935009
線程2結束執行:endTime2:1558452935009
線程3結束執行:endTime3:1558452935010

Process finished with exit code 0

運行多次,可以看到對同一個對象test的同步代碼塊和test對象的同步方法test(),都需要獲取test的監視器。反映在運行結果中,即線程2和線程3開始執行時間一定不會早於線程1結束時間,線程2和線程3誰先執行,後執行的線程開始時間不早於先執行線程的結束時間。同一個對象,多個synchronized修飾的同步方法彼此之間也是互斥的,都要先獲取對象鎖(監視器)。因此,可以看出同一個對象synchronized修飾的同步方法很多的話,互相之間具有排他性,因此應該優先考慮使用同步代碼塊,降低互斥的範圍。

      不同的對象實例的 synchronized方法是不相干擾的。也就是說,不同線程可以同時訪問相同類的不同對象實例中的synchronized方法:

package com.dxc.test.synchonized;

/**
 * 相同類的不同實例的同步方法測試
 *
 * @author dxc
 * @date 2019/5/21
 */
public class SynchronizedTest1 {

    private String name;

    SynchronizedTest1(String name) {
        this.name = name;
    }

    /**
     * 同步方法
     */
    public synchronized void testMethod() {
        System.out.println(name + "的同步方法開始執行,startTime:" + System.currentTimeMillis());
        try {
            Thread.sleep(10000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + "同步方法執行結束!endTime:" + System.currentTimeMillis());
    }

    public static void main(String[] args) {
        SynchronizedTest1 test1 = new SynchronizedTest1("test1");
        SynchronizedTest1 test2 = new SynchronizedTest1("test2");
        SynchronizedTest1 test3 = new SynchronizedTest1("test3");
        new Thread(() -> test1.testMethod()).start();
        new Thread(() -> test2.testMethod()).start();
        new Thread(() -> test3.testMethod()).start();
    }
}

某一次運行結果:

E:\jdk-11.0.2\bin\java.exe "-javaagent:E:\Intellij idea\lib\idea_rt.jar=49266:E:\Intellij idea\bin" -Dfile.encoding=UTF-8 -classpath F:\OpenTalk\blog\target\classes com.dxc.test.synchonized.SynchronizedTest1
test2的同步方法開始執行,startTime:1558454440784
test3的同步方法開始執行,startTime:1558454440784
test1的同步方法開始執行,startTime:1558454440781
test3同步方法執行結束!endTime:1558454450841
test1同步方法執行結束!endTime:1558454450841
test2同步方法執行結束!endTime:1558454450839

Process finished with exit code 0

運行多次可以看到,上面三個線程是隨機並行執行的,不存在互斥問題。

2、synchronized修飾類(Class對象)或者static成員方法

synchronized修飾類(Class對象)或者static成員方法對類的所有實例對象起作用。

測試代碼:

package com.dxc.test.synchonized;

/**
 * synchronized修飾類或者類的static方法
 *
 * @author dxc
 * @date 2019/5/25
 */
public class SynchronizedClassTest {

    private String name;

    SynchronizedClassTest(String name) {
        this.name = name;
    }

    /**
     * synchronized修飾static方法
     * */
    public synchronized static void test(SynchronizedClassTest p) {
        System.out.println(p.name + "的同步方法開始執行,startTime:" + System.currentTimeMillis());
        try {
            Thread.sleep(10000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(p.name + "同步方法執行結束!endTime:" + System.currentTimeMillis());
    }

    public static void main(String []args){
        SynchronizedClassTest test1 = new SynchronizedClassTest("test1");
        SynchronizedClassTest test2 = new SynchronizedClassTest("test2");
        SynchronizedClassTest test3 = new SynchronizedClassTest("test3");
        new Thread(() -> SynchronizedClassTest.test(test1)).start();
        new Thread(() -> SynchronizedClassTest.test(test2)).start();
        new Thread(() -> SynchronizedClassTest.test(test3)).start();
    }
}

某一次運行結果如下:

E:\jdk-11.0.2\bin\java.exe "-javaagent:E:\Intellij idea\lib\idea_rt.jar=58304:E:\Intellij idea\bin" -Dfile.encoding=UTF-8 -classpath F:\OpenTalk\blog\target\classes com.dxc.test.synchonized.SynchronizedClassTest
test1的同步方法開始執行,startTime:1558800211942
test1同步方法執行結束!endTime:1558800221990
test3的同步方法開始執行,startTime:1558800221990
test3同步方法執行結束!endTime:1558800232005
test2的同步方法開始執行,startTime:1558800232005
test2同步方法執行結束!endTime:1558800242007

Process finished with exit code 0

上述三個線程一定是串行執行的。如果某一個對象執行的是synchronized修飾的非static方法,那麼是不與synchronized修飾類或者static方法衝突的:

package com.dxc.test.synchonized;

/**
 * synchronized修飾類或者類的static方法
 *
 * @author dxc
 * @date 2019/5/25
 */
public class SynchronizedClassTest {

    private String name;

    SynchronizedClassTest(String name) {
        this.name = name;
    }

    /**
     * synchronized修飾static方法
     * */
    public synchronized static void test(SynchronizedClassTest p) {
        System.out.println(p.name + "的同步方法開始執行,startTime:" + System.currentTimeMillis());
        try {
            Thread.sleep(10000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(p.name + "同步方法執行結束!endTime:" + System.currentTimeMillis());
    }

    /**
     * synchronized修飾非static方法
     * */
    public synchronized void testNotStatic() {
        System.out.println(name + "的同步方法開始執行,startTime:" + System.currentTimeMillis());
        try {
            Thread.sleep(10000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + "同步方法執行結束!endTime:" + System.currentTimeMillis());
    }

    public static void main(String []args){
        SynchronizedClassTest test1 = new SynchronizedClassTest("test1");
        SynchronizedClassTest test2 = new SynchronizedClassTest("test2");
        SynchronizedClassTest test3 = new SynchronizedClassTest("test3");
        new Thread(() -> SynchronizedClassTest.test(test1)).start();
        new Thread(() -> test2.testNotStatic()).start();
        new Thread(() -> SynchronizedClassTest.test(test3)).start();
    }
}

程序某次運行結果:

E:\jdk-11.0.2\bin\java.exe "-javaagent:E:\Intellij idea\lib\idea_rt.jar=58370:E:\Intellij idea\bin" -Dfile.encoding=UTF-8 -classpath F:\OpenTalk\blog\target\classes com.dxc.test.synchonized.SynchronizedClassTest
test1的同步方法開始執行,startTime:1558800506557
test2的同步方法開始執行,startTime:1558800506557
test2同步方法執行結束!endTime:1558800516637
test1同步方法執行結束!endTime:1558800516637
test3的同步方法開始執行,startTime:1558800516637
test3同步方法執行結束!endTime:1558800526640

Process finished with exit code 0

從結果可以看到test2對應的線程調用synchronized修飾的非static方法是不受其他線程調用synchronized修飾static方法影響的。可以看做,前者獲取的是對象的監視器,後者獲取類的監視器。

同理synchronized(XXX.class)同步代碼塊一樣,對所有類的實例對象起作用。

     二、synchronized指令分析

synchronized修飾的同步塊與synchronized修飾的同步方法

代碼:

package com.dxc.test.synchonized;

/**
 * synchronized修飾
 *
 * @author dxc
 * @date 2019/5/26
 */
public class SynchronizedTest1 {

     /**
      * synchronized修飾非static成員方法
      * */
     public synchronized void test(){
          System.out.println("線程3同步塊開始執行:startTime3:" + System.currentTimeMillis());
     }

     public static void main(String args[]){
          //同步代碼塊
         synchronized (SynchronizedTest1.class){

         }
         //同步方法
          new SynchronizedTest1().test();
     }
}

使用javac命令得到SynchronizedTest1.class文件,javap -v SynchronizedTest1.class反編譯,得到:

F:\OpenTalk\blog\src\main\java\com\dxc\test\synchonized>javap -v SynchronizedTest1.class
Classfile /F:/OpenTalk/blog/src/main/java/com/dxc/test/synchonized/SynchronizedTest1.class
  Last modified 2019年5月26日; size 1170 bytes
  MD5 checksum 9f38aa7e4b4773e621b7d431505787cf
  Compiled from "SynchronizedTest1.java"
public class com.dxc.test.synchonized.SynchronizedTest1
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #6                          // com/dxc/test/synchonized/SynchronizedTest1
  super_class: #9                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 3, attributes: 3
Constant pool:
   #1 = Methodref          #9.#22         // java/lang/Object."<init>":()V
   #2 = Fieldref           #23.#24        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #23.#25        // java/lang/System.currentTimeMillis:()J
   #4 = InvokeDynamic      #0:#29         // #0:makeConcatWithConstants:(J)Ljava/lang/String;
   #5 = Methodref          #30.#31        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #6 = Class              #32            // com/dxc/test/synchonized/SynchronizedTest1
   #7 = Methodref          #6.#22         // com/dxc/test/synchonized/SynchronizedTest1."<init>":()V
   #8 = Methodref          #6.#33         // com/dxc/test/synchonized/SynchronizedTest1.test:()V
   #9 = Class              #34            // java/lang/Object
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               test
  #15 = Utf8               main
  #16 = Utf8               ([Ljava/lang/String;)V
  #17 = Utf8               StackMapTable
  #18 = Class              #35            // "[Ljava/lang/String;"
  #19 = Class              #36            // java/lang/Throwable
  #20 = Utf8               SourceFile
  #21 = Utf8               SynchronizedTest1.java
  #22 = NameAndType        #10:#11        // "<init>":()V
  #23 = Class              #37            // java/lang/System
  #24 = NameAndType        #38:#39        // out:Ljava/io/PrintStream;
  #25 = NameAndType        #40:#41        // currentTimeMillis:()J
  #26 = Utf8               BootstrapMethods
  #27 = MethodHandle       6:#42          // REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;L
java/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #28 = String             #43            // 線程3同步塊開始執行:startTime3:\u0001
  #29 = NameAndType        #44:#45        // makeConcatWithConstants:(J)Ljava/lang/String;
  #30 = Class              #46            // java/io/PrintStream
  #31 = NameAndType        #47:#48        // println:(Ljava/lang/String;)V
  #32 = Utf8               com/dxc/test/synchonized/SynchronizedTest1
  #33 = NameAndType        #14:#11        // test:()V
  #34 = Utf8               java/lang/Object
  #35 = Utf8               [Ljava/lang/String;
  #36 = Utf8               java/lang/Throwable
  #37 = Utf8               java/lang/System
  #38 = Utf8               out
  #39 = Utf8               Ljava/io/PrintStream;
  #40 = Utf8               currentTimeMillis
  #41 = Utf8               ()J
  #42 = Methodref          #49.#50        // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;
[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #43 = Utf8               線程3同步塊開始執行:startTime3:\u0001
  #44 = Utf8               makeConcatWithConstants
  #45 = Utf8               (J)Ljava/lang/String;
  #46 = Utf8               java/io/PrintStream
  #47 = Utf8               println
  #48 = Utf8               (Ljava/lang/String;)V
  #49 = Class              #51            // java/lang/invoke/StringConcatFactory
  #50 = NameAndType        #44:#55        // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke
/CallSite;
  #51 = Utf8               java/lang/invoke/StringConcatFactory
  #52 = Class              #57            // java/lang/invoke/MethodHandles$Lookup
  #53 = Utf8               Lookup
  #54 = Utf8               InnerClasses
  #55 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #56 = Class              #58            // java/lang/invoke/MethodHandles
  #57 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #58 = Utf8               java/lang/invoke/MethodHandles
{
  public com.dxc.test.synchonized.SynchronizedTest1();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 9: 0

  public synchronized void test();
    descriptor: ()V
    flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: invokestatic  #3                  // Method java/lang/System.currentTimeMillis:()J
         6: invokedynamic #4,  0              // InvokeDynamic #0:makeConcatWithConstants:(J)Ljava/lang/String;
        11: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        14: return
      LineNumberTable:
        line 15: 0
        line 16: 14

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #6                  // class com/dxc/test/synchonized/SynchronizedTest1
         2: dup
         3: astore_1
         4: monitorenter
         5: aload_1
         6: monitorexit
         7: goto          15
        10: astore_2
        11: aload_1
        12: monitorexit
        13: aload_2
        14: athrow
        15: new           #6                  // class com/dxc/test/synchonized/SynchronizedTest1
        18: dup
        19: invokespecial #7                  // Method "<init>":()V
        22: invokevirtual #8                  // Method test:()V
        25: return
      Exception table:
         from    to  target type
             5     7    10   any
            10    13    10   any
      LineNumberTable:
        line 20: 0
        line 22: 5
        line 24: 15
        line 25: 25
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 10
          locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}
SourceFile: "SynchronizedTest1.java"
InnerClasses:
  public static final #53= #52 of #56;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #27 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;
)Ljava/lang/invoke/CallSite;
    Method arguments:
      #28 線程3同步塊開始執行:startTime3:\u0001

F:\OpenTalk\blog\src\main\java\com\dxc\test\synchonized>

可以看到synchronized對同步塊的實現使用了monitorenter和monitorexit指令:

       3: astore_1
       4: monitorenter
       5: aload_1
       6: monitorexit
       7: goto          15

synchronized同步方法則是通過方法修飾符上的ACC_AYNCHRONIZED實現的:

  public synchronized void test();
    descriptor: ()V
    flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=3, locals=1, args_size=1

無論採用的是哪一種方式,本質都是獲取對象監視器。

代碼地址:https://github.com/WhiteBookMan1994/OpenTalk/tree/master/blog/src/main/java/com/dxc/test/synchonized

參考資料:《Java 併發編程的藝術》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章