【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."":()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."":()V
   #8 = Methodref          #6.#33         // com/dxc/test/synchonized/SynchronizedTest1.test:()V
   #9 = Class              #34            // java/lang/Object
  #10 = Utf8               
  #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        // "":()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."":()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 "":()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 并发编程的艺术》

你可能感兴趣的:(Java)