Synchronized实现原理(方法代码块)

简介: Synchronized实现原理(方法代码块)

转自:blog.csdn.net/qq_34894975…

我们都知道synchronized可以修饰方法和代码块,那么这两者的内部实现原理是相同的吗?我们来仔细看一下。

修饰方法和代码块的不同

首先我们先看一下通过反编译出的字节码两者有何不同。

源代码如下:

package jvm;
 
/**
 * Created by ljm on 29/1/2018.
 */
public class ClassCompile {
    synchronized void test(){}
    void test1(){
        synchronized (ClassCompile.class){
 
        }
    }
}

我们使用命令javap来反编译:

javap -verbose ClassCompile

得到的字节码如下(这里只截取与这里所说有关的部分):

synchronized void test();    descriptor: ()V    flags: ACC_SYNCHRONIZED    Code:      stack=0, locals=1, args_size=1         0: return      LineNumberTable:        line 7: 0      LocalVariableTable:        Start  Length  Slot  Name   Signature            0       1     0  this   Ljvm/ClassCompile; void test1();    descriptor: ()V    flags:    Code:      stack=2, locals=3, args_size=1         0: ldc           #2                  // class jvm/ClassCompile         2: dup         3: astore_1         4: monitorenter         5: aload_1         6: monitorexit         7: goto          15        10: astore_2        11: aload_1        12: monitorexit        13: aload_2        14: athrow        15: return      Exception table:         from    to  target type             5     7    10   any            10    13    10   any

我们发现两者在处理上是有不同的。

  • 同步方法,JVM使用ACC_SYNCHRONIZED标识来实现。即JVM通过在方法访问标识符(flags)中加入ACC_SYNCHRONIZED来实现同步功能。
  • 同步代码块,JVM使用monitorentermonitorexit两个指令实现同步。即JVM为代码块的前后真正生成了两个字节码指令来实现同步功能的。
    然后我们分别对这两个做详细解释。

同步方法

The Java® Virtual Machine Specification针对同步方法的说明:

Method-level synchronization is performed implicitly, as part of method invocation and return. A synchronized method is distinguished in the run-time constant pool’s method_info structure by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED is set, the executing thread enters a monitor, invokes the method itself, and exits the monitor whether the method invocation completes normally or abruptly. During the time the executing thread owns the monitor, no other thread may enter it. If an exception is thrown during invocation of the synchronized method and the synchronized method does not handle the exception, the monitor for the method is automatically exited before the exception is rethrown out of the synchronized method.

这段话适合好好读读,大致含义如下:

同步方法是隐式的。一个同步方法会在运行时常量池中的method_info结构体中存放ACC_SYNCHRONIZED标识符。当一个线程访问方法时,会去检查是否存在ACC_SYNCHRONIZED标识,如果存在,则先要获得对应的monitor锁,然后执行方法。当方法执行结束(不管是正常return还是抛出异常)都会释放对应的monitor锁。如果此时有其他线程也想要访问这个方法时,会因得不到monitor锁而阻塞。当同步方法中抛出异常且方法内没有捕获,则在向外抛出时会先释放已获得的monitor锁

第一句话解释一下:

同步方法是隐式的。一个同步方法会在运行时常量池中的method_info结构体中存放ACC_SYNCHRONIZED标识符。

这句话的解读是:我们从上面的反编译结果也可以看到,同步方法会在class文件中的access_flags中存放ACC_SYNCHRONIZED,那这句话为什么说在运行时常量池中呢?

答:我们看看ACC_SYNCHRONIZED在class文件中的位置如下:

ACC_SYNCHRONIZE在class文件中的位置

可以看到ACC_SYNCHRONIZED标识存放在常量池中,而method_info结构体中的access_flags字段是u2的,所以它只是一个指向常量池的标记。而常量池在加载时就会加载到运行时常量池中。所以这里解释了为什么上面说ACC_SYNCHRONIZED在运行时常量池中,而看class文件是存放在access_flags中的道理。

同步代码块

同步代码块使用monitorentermonitorexit两个指令实现同步, The Java® Virtual Machine Specification中有关于这两个指令的介绍:

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维护着一个记录着拥有次数的计数器。未被拥有的monitor的该计数器为0,当一个线程获得monitor(执行monitorenter)后,该计数器自增变为 1 。
  • 当同一个线程再次获得该monitor的时候,计数器再次自增;
  • 当不同线程想要获得该monitor的时候,就会被阻塞。
  1. 当同一个线程释放 monitor(执行monitorexit指令)的时候,计数器再自减。当计数器为0的时候。monitor将被释放,其他线程便可以获得monitor。

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指令时,会去讲monitor的计数器减一,如果结果是0,则该线程将不再拥有该monitor。其他线程就可以获得该monitor了。

总结一下

  • 同步方法和同步代码块底层都是通过monitor来实现同步的。
  • 两者的区别:同步方式是通过方法中的access_flags中设置ACC_SYNCHRONIZED标志来实现;同步代码块是通过monitorenter和monitorexit来实现
  • 我们知道了每个对象都与一个monitor相关联。而monitor可以被线程拥有或释放。

至此我们还有两个问题没有搞清楚:

  1. 到底啥是monitor?
  2. 每个对象是如何与monitor关联的?


目录
相关文章
|
设计模式 存储 缓存
Java中的抽象类、接口、设计模式、包装类和泛型(附带相关面试题)
一.抽象类(abstract),二.接口(interface),三.设计模式,四.包装类,五.泛型
644 0
|
4月前
|
监控 容灾 测试技术
第三方API的稳定性如何保障?
第三方API的稳定性对电商至关重要,直接影响业务连续性和用户体验。为确保稳定,可采取以下措施:选择可靠提供商、评估技术架构高可用性、实现限流重试机制、实时监控预警、充分测试、优化网络连接、使用API网关、保持与提供商沟通、制定应急预案及定期评估改进。这些方法能有效提升API稳定性,保障业务高效运行和用户满意度。
130 0
|
7月前
|
消息中间件 架构师 Apache
一本书精通Apache RocketMQ
一本书精通Apache RocketMQ
263 3
|
存储 关系型数据库 MySQL
RR隔离级别在MySQL中的实现与幻读问题探讨
【10月更文挑战第3天】在数据库管理系统中,事务隔离级别是确保数据一致性和并发性能的关键要素。MySQL作为广泛使用的关系型数据库管理系统,支持多种事务隔离级别,其中可重复读(Repeatable Read,简称RR)是其默认隔离级别。本文将深入探讨RR隔离级别在MySQL中的实现原理,以及RR隔离级别下幻读问题的产生与解决方案。
452 2
|
算法 索引
让星星⭐月亮告诉你,HashMap的resize()即扩容方法源码解读(已重新完善,如有不足之处,欢迎指正~)
`HashMap`的`resize()`方法主要用于数组扩容,包括初始化或加倍数组容量。该方法首先计算新的数组容量和扩容阈值,然后创建新数组。接着,旧数组中的数据根据`(e.hash & oldCap)`是否等于0被重新分配到新数组中,分为低位区和高位区两个链表,确保数据迁移时的正确性和高效性。
222 3
|
数据采集 安全 数据可视化
浅谈网络安全态势感知
网络安全态势感知是应对复杂网络环境和频繁攻击的关键,它基于数据采集、理解和预测,评估网络安全性并预测未来趋势。中国在该领域的研究已有显著进展,如2003年国家互联网应急中心的监测平台和2015年四川大学的大数据分析平台。态势感知涉及数据采集、理解、评估和预测四个步骤,通过可视化方式展示给用户。其核心功能包括态势总览、资产管理、告警管理等。未来发展方向将结合大数据和AI提升预测精度,实现云化和动态扩展。德迅云安全提供的解决方案侧重主动监测、精准防护和智能分析,适用于金融、电商和政企等领域。
|
SQL 存储 监控
(十一)MySQL日志篇之undo-log、redo-log、bin-log.....傻傻分不清!
任何项目都会有日志,MySQL也不例外,而且MySQL更是其中的佼佼者,日志种类繁多,而本篇的目的就是全解MySQL中的各类日志,如撤销日志、错误日志、慢查询日志、中继日志、回滚日志.....
1225 2
|
缓存 算法 Java
这些年背过的面试题——JVM篇
本文是技术人面试系列JVM篇,面试中关于JVM都需要了解哪些基础?一文带你详细了解,欢迎收藏!
|
机器学习/深度学习 编解码 算法
深度学习之边缘检测
边缘检测是计算机视觉中的一项基本任务,旨在识别图像中像素值变化显著的区域,即边缘。传统的边缘检测算法(如Sobel、Canny等)通过滤波器和梯度运算来检测边缘,而基于深度学习的方法则通过训练神经网络自动学习图像中的边缘特征,从而实现更高的检测精度和鲁棒性。
493 1
|
消息中间件 关系型数据库 MySQL
如何保证消息幂等
如何保证消息幂等