专栏首页JAVA乐园被问到傻傻不懂synchronized底层原理

被问到傻傻不懂synchronized底层原理

0x01:synchronized的基本语法

修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

0x02:代码分析synchronized

package com.lesson8;

public class SynchronizedDemo {

    public Object lock = new Object();

    public synchronized void  syncCommonMethod(){
        System.out.println("==syncCommonMethod==");
    }

    public static synchronized void  syncStaticMethod(){
        System.out.println("==syncStaticMethod==");
    }

    public void  syncBlockCode(){
        synchronized (lock) {
            System.out.println("==syncBlockCode==");
        }
    }

}

代码中包三个方法,分别是synchronized修饰实例方法、synchronized静态方法synchronized修饰代码块。使用命令:

javap -v SynchronizedDemo.class

得到如下汇编指令:

Classfile /D:/jmeterws/xml/com-lesson8/target/classes/com/lesson8/SynchronizedDemo.class
  Last modified 2020-4-5; size 926 bytes
  MD5 checksum f23cff99c70b8a401db1cc3bd74538ec
  Compiled from "SynchronizedDemo.java"
public class com.lesson8.SynchronizedDemo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // com/lesson8/SynchronizedDemo
   #2 = Utf8               com/lesson8/SynchronizedDemo
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               lock
   #6 = Utf8               Ljava/lang/Object;
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Methodref          #3.#11         // java/lang/Object."<init>":()V
  #11 = NameAndType        #7:#8          // "<init>":()V
  #12 = Fieldref           #1.#13         // com/lesson8/SynchronizedDemo.lock:Ljava/lang/Object;
  #13 = NameAndType        #5:#6          // lock:Ljava/lang/Object;
  #14 = Utf8               LineNumberTable
  #15 = Utf8               LocalVariableTable
  #16 = Utf8               this
  #17 = Utf8               Lcom/lesson8/SynchronizedDemo;
  #18 = Utf8               syncCommonMethod
  #19 = Fieldref           #20.#22        // java/lang/System.out:Ljava/io/PrintStream;
  #20 = Class              #21            // java/lang/System
  #21 = Utf8               java/lang/System
  #22 = NameAndType        #23:#24        // out:Ljava/io/PrintStream;
  #23 = Utf8               out
  #24 = Utf8               Ljava/io/PrintStream;
  #25 = String             #26            // ==syncCommonMethod==
  #26 = Utf8               ==syncCommonMethod==
  #27 = Methodref          #28.#30        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #28 = Class              #29            // java/io/PrintStream
  #29 = Utf8               java/io/PrintStream
  #30 = NameAndType        #31:#32        // println:(Ljava/lang/String;)V
  #31 = Utf8               println
  #32 = Utf8               (Ljava/lang/String;)V
  #33 = Utf8               syncStaticMethod
  #34 = String             #35            // ==syncStaticMethod==
  #35 = Utf8               ==syncStaticMethod==
  #36 = Utf8               syncBlockCode
  #37 = String             #38            // ==syncBlockCode==
  #38 = Utf8               ==syncBlockCode==
  #39 = Utf8               StackMapTable
  #40 = Class              #41            // java/lang/Throwable
  #41 = Utf8               java/lang/Throwable
  #42 = Utf8               SourceFile
  #43 = Utf8               SynchronizedDemo.java
{
  public java.lang.Object lock;
    descriptor: Ljava/lang/Object;
    flags: ACC_PUBLIC

  public com.lesson8.SynchronizedDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: invokespecial #10                 // Method java/lang/Object."<init>":()V
         4: aload_0
         5: new           #3                  // class java/lang/Object
         8: dup
         9: invokespecial #10                 // Method java/lang/Object."<init>":()V
        12: putfield      #12                 // Field lock:Ljava/lang/Object;
        15: return
      LineNumberTable:
        line 3: 0
        line 5: 4
        line 3: 15
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  this   Lcom/lesson8/SynchronizedDemo;

  public synchronized void syncCommonMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #19                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #25                 // String ==syncCommonMethod==
         5: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 8: 0
        line 9: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/lesson8/SynchronizedDemo;

  public static synchronized void syncStaticMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #19                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #34                 // String ==syncStaticMethod==
         5: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 12: 0
        line 13: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature

  public void syncBlockCode();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=1
         0: aload_0
         1: getfield      #12                 // Field lock:Ljava/lang/Object;
         4: dup
         5: astore_1
         6: monitorenter
         7: getstatic     #19                 // Field java/lang/System.out:Ljava/io/PrintStream;
        10: ldc           #37                 // String ==syncBlockCode==
        12: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        15: aload_1
        16: monitorexit
        17: goto          23
        20: aload_1
        21: monitorexit
        22: athrow
        23: return
      Exception table:
         from    to  target type
             7    17    20   any
            20    22    20   any
      LineNumberTable:
        line 16: 0
        line 17: 7
        line 16: 15
        line 19: 23
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      24     0  this   Lcom/lesson8/SynchronizedDemo;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 20
          locals = [ class com/lesson8/SynchronizedDemo, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 2
}
SourceFile: "SynchronizedDemo.java"

分析如上代码发现synchronized修饰实例方法、synchronized静态方法synchronized修饰代码块不一样,synchronized修饰实例方法和synchronized静态方法一样

synchronized修饰代码块

涉及两条指令:

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

如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。

如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

  • monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的所有者。

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

synchronized修饰实例方法

synchronized静态方法

从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现)。相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。

JVM就是根据该标示符来实现方法的同步的:当方法被调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

本文分享自微信公众号 - JAVA乐园(happyhuangjinjin88),作者:java乐园

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • mybatis中foreach collection三种用法

    在做mybatis的mapper.xml文件的时候,时常遇到一些需要批量操作的情况,这个时候mybatis的foreach标签就派上用场了。

    java乐园
  • 生成安全的随机数

    Math.random()产生的随机数是在0 到1之间的一个double类型的随机数,即 0 <= random <= 1

    java乐园
  • Linux下查看进程线程数的方法

    rsyslogd这个进程有5个线程,所以ps -ef只有一行,而ps -eLf就有5行

    java乐园
  • 条件随机场(Conditional Random Field,CRF)

    概率无向图模型(probabilistic undirected graphical model),又称为马尔可夫随机场(Markov random field...

    Michael阿明
  • 介绍一款好用的linux本地与远程数据传输工具

    我们租用云主机或在本地使用虚机建立一台CentOS之类的Linux系统后,一般需要安装很多组件,但用yum安装速度较慢;或者本地电脑有一些已下载...

    希望的田野
  • Python 15.2 POP3 收取邮

    收取邮件就是编写一个MUA作为客户端,从MDA把邮件获取到用户的电脑或者手机上。收取邮件最常用的就是POP3协议。

    py3study
  • 灵活使用python的基础数据类型实现数据分析的效果

    云飞
  • 新一代直播传输协议SRT

    https://www2.tutormeetplus.com/v2/render/playback?mode=playback&token=a1564111ef...

    LiveVideoStack
  • 行业趋势 | 2017年中国教育培训行业白皮书

    ? ? 2017年11月15日,教育培训行业年度重点大会──GET 2017 教育科技大会,在北京国际会议中心盛大举行。腾讯网络媒体事业群教育行业群总监陈世洪...

    腾讯企点
  • Spring多数据源事务

    接着上一篇文章Spring事务基础,本文主要是关于Spring多数据源的情况下如何保证事务正常回滚。这里也是使用大家广泛使用的jta-atomikos进行,我只...

    用户3467126

扫码关注云+社区

领取腾讯云代金券