Synchronized方法or代码块?

疾风知劲草,板荡识诚臣,
勇夫安识义,智者必怀仁.

无论是开发还是面试,作为自认为不再是菜鸟的我们,在并发编程中,我们会理所当然地认为:

synchronized关键字所修饰的区域应该是越小越好.(synchronized是互斥锁,即重量级锁,相对开销 会比较大)

有兴趣的读者,可以参看本人的这篇博客: 关于synchronized的面试题

能坚持读到这里的朋友,此时心中肯定会有疑问:
在这里插入图片描述

莫非我错了?

这里暂且不论对错,先继续往下看…


1. synchronized方法


/**
 * @Author Bertking
 */
public class SyncDemo {
    int count;
    // 注意这里synchronized是修饰方法的
    public synchronized void add(){
        count ++;
    }
}

通过编译之后,查看字节码(ByteCode):
输出内容如下:

Classfile /home/mi/IdeaProjects/jols/src/constant_pool/SyncDemo.class
  Last modified 2020-8-6; size 293 bytes
  MD5 checksum 7e99f0f7d34ed583b75e92303126db7a
  Compiled from "SyncDemo.java"
public class constant_pool.SyncDemo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#15         // constant_pool/SyncDemo.count:I
   #3 = Class              #16            // constant_pool/SyncDemo
   #4 = Class              #17            // java/lang/Object
   #5 = Utf8               count
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               add
  #12 = Utf8               SourceFile
  #13 = Utf8               SyncDemo.java
  #14 = NameAndType        #7:#8          // "<init>":()V
  #15 = NameAndType        #5:#6          // count:I
  #16 = Utf8               constant_pool/SyncDemo
  #17 = Utf8               java/lang/Object
{
  int count;
    descriptor: I
    flags:

  public constant_pool.SyncDemo();
    descriptor: ()V
    flags: 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 8: 0

  public synchronized void add();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED// 注意这里的ACC_SYNCHRONIZED
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field count:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field count:I
        10: return
      LineNumberTable:
        line 11: 0
        line 12: 10
}
SourceFile: "SyncDemo.java"

2. synchronized 代码块


/**
 * @Author Bertking
 */
public class SyncDemo {
    int count;
    public void add(){
    	// synchronized修饰代码块
        synchronized (this){
            count ++;
        }
    }
}

字节码如下:

Classfile /home/mi/IdeaProjects/jols/src/constant_pool/SyncDemo.class
  Last modified 2020-8-6; size 405 bytes
  MD5 checksum 189776096818983fb3e874b2204e0fd3
  Compiled from "SyncDemo.java"
public class constant_pool.SyncDemo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#18         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#19         // constant_pool/SyncDemo.count:I
   #3 = Class              #20            // constant_pool/SyncDemo
   #4 = Class              #21            // java/lang/Object
   #5 = Utf8               count
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               add
  #12 = Utf8               StackMapTable
  #13 = Class              #20            // constant_pool/SyncDemo
  #14 = Class              #21            // java/lang/Object
  #15 = Class              #22            // java/lang/Throwable
  #16 = Utf8               SourceFile
  #17 = Utf8               SyncDemo.java
  #18 = NameAndType        #7:#8          // "<init>":()V
  #19 = NameAndType        #5:#6          // count:I
  #20 = Utf8               constant_pool/SyncDemo
  #21 = Utf8               java/lang/Object
  #22 = Utf8               java/lang/Throwable
{
  int count;
    descriptor: I
    flags:

  public constant_pool.SyncDemo();
    descriptor: ()V
    flags: 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 8: 0

  public void add();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter // monitorenter
         4: aload_0
         5: dup
         6: getfield      #2                  // Field count:I
         9: iconst_1
        10: iadd
        11: putfield      #2                  // Field count:I
        14: aload_1
        15: monitorexit. // monitorenter
        16: goto          24
        19: astore_2
        20: aload_1
        21: monitorexit
        22: aload_2
        23: athrow
        24: return
      Exception table:
         from    to  target type
             4    16    19   any
            19    22    19   any
      LineNumberTable:
        line 11: 0
        line 12: 4
        line 13: 14
        line 14: 24
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 19
          locals = [ class constant_pool/SyncDemo, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}
SourceFile: "SyncDemo.java"

我们会看到同样的add方法经过编译器编译后的代码量相差较大,且实现不一样。

3. 对比

synchronized方法的字节码中方法访问标识中有ACC_SYNCHRONIZED,JVM也是根据该标志直接判断是否需要同步的。

synchronized代码块的字节码中是用**监视器(monitor)**来实现同步的,且方法中含有多次出栈入栈操作。

显然,synchronized方法 比 synchronized代码块 的VM执行效率要高。根据官方的数据,大概是快13%左右。

但是我们不能就因此否定之前的逻辑(synchronized修饰的区域越小越好)。这个需要我们自己去衡量其中的利弊。

4.总结

正如我们上面所看到的,对于synchronized方法被编译后,其methods的实现和普通方法一样,在VM的字节码层面并没有任何特别的指令来实现synchronized方法,仅仅是在methods的access_flags中多了一个ACC_SYNCHRONIZED,告知VM这是个同步方法.

而对于synchronized代码块将会在同步块的入口位置和出口位置分别插入monitorentermonitorexit字节码指令.
在这里插入图片描述


写在最后,理解Java ByteCode的重要性不言而喻。本人也是在开发Java多年之后,又深入研究了The Java® Virtual Machine Specification.才感觉自己算是真正走进了Java的世界。本人考虑后期开个公众号,来专门把Java底层的东西给写出来。

暂时没有想到好的公众号名字,所以还在筹备中,如果你有好的建议,希望下方留言。不甚感激。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值