synchronized浅析

synchronized是什么?

synchronized是一个关键字,并不是一个“锁”,“加锁”这个操作更符合它的含义。

字面意思它是同步的过去式。

多线程环境:

原理

synchronized的底层实现是使用操作系统的mutex lock实现的。JDK5之前被称为重量级锁,JDK6对synchronized内在机制进行了优化,加入了CAS、轻量级锁和偏向锁对功能,性能上已经跟ReentrantLock相差无几,而且在使用上更简单,不易出错。所以如果仅仅要实现互斥效果,不需要基于Lock的复杂操作(中断、条件等),推荐优先使用synchronized。

synchronized用的锁是存在Java对象头里的。JVM基于进入退出Monitor对象来实现方法同步和代码块同步。

synchronized的几种加锁方式以及基础说明

修饰内容锁类型示例
没加锁没加锁示例1
修饰代码块任意对象锁示例2
修饰普通方法this锁示例3
修饰静态方法类锁示例4

示例1:没有synchronized加锁

public class NoSynchronizedDemo {
    public void method() {
        System.out.println("Method 1 start");
    }
}

查看核心字节码

public void method();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Method 1 start
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/lhx/cloud/javathread/NoSynchronizedDemo;

示例2:同步方法块,锁是括号里面的对象

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("Method 1 start");
        }
    }
}

查看字节码

public void method();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #3                  // String Method 1 start
         9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: aload_1
        13: monitorexit
        14: goto          22
        17: astore_2
        18: aload_1
        19: monitorexit
        20: aload_2
        21: athrow
        22: return

可以看在加锁的代码块,多了个 monitorenter , monitorexit

monitorenter
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

  • 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
  • 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
  • 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权

总结:

  • synchronized是可重入锁,即如果当前线程以获得锁对象,可再次获取该锁对象。即:该锁对象的监视器锁 monitor 具有可重入性,每进入一次,进入次数+1
  • 从synchronized使用的语法上,如果修饰代码块,synchronize (object) {} object 即为锁对象
  • 如果修饰方法,普通方法可认为是 this 锁,即当前对象锁
  • 静态方法可认为是类锁

monitorexit

  • 执行monitorexit的线程必须是objectref所对应的monitor的所有者。
  • 指令执行时,monitor的进入数减1
  • 如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者
  • 其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权

总结:
通过以上描述,应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

示例3:普通同步方法,锁是当前实例对象

public class SynchronizedDemo2 {
    public synchronized void method() {
        System.out.println("Method 1 start");
    }
}

查看字节码

public synchronized void method();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Method 1 start
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return

注意: 在flags上增加了ACC_SYNCHRONIZED

示例4:静态同步方法,锁是当前类的class对象

public class SynchronizedDemoStatic {
    public static synchronized void method() {
        System.out.println("Method 1 start");
    }
}

查看字节码

public static synchronized void method();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Method 1 start
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return

注意: 在flags上增加了ACC_STATIC, ACC_SYNCHRONIZED

总结:

针对示例3、示例四,在flags上均增加了ACC_SYNCHRONIZED

从反编译的结果来看,方法的同步并没有通过指令monitorentermonitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法(没加synchronized的),其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值