前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >通过jvm字节码研究Synchronized

通过jvm字节码研究Synchronized

作者头像
公众号 IT老哥
修改2020-09-21 15:34:30
5380
修改2020-09-21 15:34:30
举报
文章被收录于专栏:用户7621540的专栏

本文源自 公-众-号 IT老哥 的分享

IT老哥,一个在大厂做高级Java开发的程序员,每天分享技术干货文章

哈喽,小伙伴你们好,我是IT老哥

我们今天来真正的看一看Synchronized在jvm层面如何实现同步锁的

话不多说,直接上代码

这是一个Synchronized几种用法的java类:

代码语言:javascript
复制
public class SyncTest {

    public synchronized void methodOne() {

    }

    public void methodTwo() {
        synchronized (this) {
        }
    }

    public synchronized static void methodThree() {

    }
}

将上面的SyncTest 编译成class文件

javac SyncTest.java ——> SyncTest.class

class文件我们是不能直接进行观看的,得把他编译成字节码文件

我们通过这个命令:

javap -v SyncTest.class

对javap这个命令陌生的小伙伴可以去了解一下

这就是编译好的字节码文件了:

代码语言:javascript
复制
{
  public com.bilibili.itlaoge.juc.SyncTest();
    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 3: 0

  public synchronized void methodOne();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED // 同步锁的关键
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 7: 0

  public void methodTwo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter // 进入锁
         4: aload_1
         5: monitorexit // 释放锁
         6: goto          14
         9: astore_2
        10: aload_1
        11: monitorexit // 保证在发生异常的情况下,也能正常的释放锁
        12: aload_2
        13: athrow
        14: return
      Exception table:
         from    to  target type
             4     6     9   any
             9    12     9   any
      LineNumberTable:
        line 10: 0
        line 11: 4
        line 12: 14
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 9
          locals = [ class com/bilibili/itlaoge/juc/SyncTest, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

  public static synchronized void methodThree();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED // 同步锁的关键
    Code:
      stack=0, locals=0, args_size=0
         0: return
      LineNumberTable:
        line 16: 0
}

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

从官网找到jvm关于monitorenter 的一个描述如下:

代码语言:javascript
复制
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

大概意思是:

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

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

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

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

monitorexit:

代码语言:javascript
复制
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

这段话的大概意思为:

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

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

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

大致了解了synchronized 的原理,下面给大家出几道题玩玩

大家判断一下,这几种情况有没有存在锁竞争的问题

第一题:

代码语言:javascript
复制
public class SynchronizedTest {
    public synchronized void methodOne(){
        System.out.println("Method One start");
        try {
            System.out.println("Method One execute");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method One end");
    }

    public synchronized void methodTwo(){
        System.out.println("Method Two start");
        try {
            System.out.println("Method Two execute");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method Two end");
    }

    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.methodOne();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.methodTwo();
            }
        }).start();
    }
}

第二题:

代码语言:javascript
复制
public class SynchronizedTest2 {
     public static synchronized void methodOne(){
         System.out.println("Method One start");
         try {
             System.out.println("Method One execute");
             Thread.sleep(2000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("Method One end");
     }
 
     public static synchronized void methodTwo(){
         System.out.println("Method Two start");
         try {
             System.out.println("Method Two execute");
             Thread.sleep(2000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("Method Two end");
     }
  
     public static void main(String[] args) {
         final SynchronizedTest test = new SynchronizedTest();
         final SynchronizedTest testTwo = new SynchronizedTest();
 
         new Thread(new Runnable() {
             @Override
             public void run() {
                 test.methodOne();
             }
         }).start();
 
         new Thread(new Runnable() {
             @Override
             public void run() {
                 testTwo.methodTwo();
             }
         }).start();
     }
 }

第三题:

代码语言:javascript
复制
public class SynchronizedTest3 {
    public void methodOne(){
        System.out.println("Method One start");
        try {
            synchronized (this) {
                System.out.println("Method One execute");
                Thread.sleep(2000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method One end");
    }

    public void methodTwo(){
        System.out.println("Method Two start");
        try {
            synchronized (this) {
                System.out.println("Method Two execute");
                Thread.sleep(2000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method Two end");
    }

    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.methodOne();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.methodTwo();
            }
        }).start();
    }
}

大家要认真作答呦,哈哈

云服务器云硬盘数据库(包括MySQL、Redis、MongoDB、SQL Server),CDN流量包,短信流量包,cos资源包,消息队列ckafka,点播资源包,实时音视频套餐,网站管家(WAF),大禹BGP高防(包含高防包及高防IP),云解析SSL证书,手游安全MTP移动应用安全云直播等等。

代码语言:javascript
复制
给个[在看],是对IT老哥最大的支持
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IT老哥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本文源自 公-众-号 IT老哥 的分享
相关产品与服务
实时音视频
实时音视频(Tencent RTC)基于腾讯21年来在网络与音视频技术上的深度积累,以多人音视频通话和低延时互动直播两大场景化方案,通过腾讯云服务向开发者开放,致力于帮助开发者快速搭建低成本、低延时、高品质的音视频互动解决方案。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档