Synchronized实现原理及锁升级

Synchronized实现原理及锁升级

Synchronized是Java内置的机制,是JVM层面的,而Lock则是接口,JDK层面的。最初的Synchronized的性能效率比较差,但是随着版本的升级,Synchronized已经越来越强大。

  • 修饰普通方法,使用类的实例加锁,进入方法前需要获取当前类的实例锁;

    注意反编译后的内容,flags那一行多了ACC_SYNCHRONIZED标识,表示这是同步方法。

    	public synchronized void demo1() {
            System.out.println("demo1");
        }
    
      public synchronized void demo1();
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_SYNCHRONIZED
        Code:
          stack=2, locals=1, args_size=1
             0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: ldc           #7                  // String demo1
             5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
             8: return
    
  • 修饰静态方法,使用当前类加锁,进入方法前需要获取当前类对象锁;

    注意反编译后的内容,flags那一行多了ACC_SYNCHRONIZED标识,表示这是同步方法。

    	public synchronized static void demo(){
            System.out.println("demo");
        }
    
     public static synchronized void demo();
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
        Code:
          stack=2, locals=0, args_size=0
             0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: ldc           #6                  // String demo
             5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
             8: return
    
    
  • 修饰代码块,使用对括号里面配置的锁;

    在开启锁和结束锁的位置分别有monitorenter、monitorexit两个指令,也就是说Synchronized同步代码块是基于monitorenter加锁和monitorexit解锁来操作的。

    方法上加入Synchronized关键字,编译后没有使用上面这两个指令,而是通过ACC_SYNCHRONIZED标识这是同步方法。

    	public static void main(String[] args) {
            synchronized (Demo.class) {
                System.out.println("main");
            }
        }
    
     public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=3, args_size=1
             0: ldc           #2                  // class com/Demo
             2: dup
             3: astore_1
             4: monitorenter
             5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
             8: ldc           #4                  // String main
            10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            13: aload_1
            14: monitorexit
            15: goto          23
            18: astore_2
            19: aload_1
            20: monitorexit
            21: aload_2
            22: athrow
            23: return
    

注意Synchronized是可重入的锁,同一个线程可多次获取锁,如果当前线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1,如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

Java对象头

Synchronized用的锁存在Java对象头中,Java对象头里的Mark Word默认存储对象的HashCode、分代年龄和锁标记位。在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。

锁一共有四种状态,级别从低到高分别为:无锁–>偏向锁–>轻量级锁–>重量级锁,这几个状态随着竞争情况逐渐升级。为提高获取锁和释放锁的效率,锁可以升级但不能降级。

  • 无锁

    ​ 没有对资源进行锁定,所有线程都能够访问并修改同一资源,但同时修改成功的只有一个线程,其他修改失败的线程会不断重试知道修改成功。

  • 偏向锁

    ​ 是指偏向第一个加锁线程,该线程是不会主动释放偏向锁的,只有当其他线程尝试竞争偏向锁时才会被释放。

    ​ 偏向锁的撤销,需要在某个时间点上没有字节码正在执行时,先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态,如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁;如果线程处于活动状态,则升级成为轻量级锁状态。

  • 轻量级锁

    ​ 是指当锁是偏向锁的时候,被第二个线程B所访问,此时偏向锁就会升级为轻量级锁,线程B会通过自旋的形式尝试获取锁,线程不会阻塞,从而节省线程切换的性能。

    ​ 当只有一个等待线程,则该线程将通过自旋进行等待,但是当自旋超过一定的次数时,轻量级锁便会升级为重量级锁;当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁。

    ​ JDK采用的是适应性自旋,简单来说就是线程如果自旋成功了,则下次自旋次数会更多,如果自旋失败了,则下次自旋次数就会减少。

  • 重量级锁

    ​ 重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。

    ​ 重量级锁通过对象内部的监视器(monitor)实现,而其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。

你可能感兴趣的:(JVM,Java)