首页 > 编程语言 >JUC源码解析: 深入解读 synchronize锁!

JUC源码解析: 深入解读 synchronize锁!

时间:2024-03-22 17:24:25浏览次数:33  
标签:lang JUC java Utf8 sync synchronize 源码 线程 Main

JUC源码解析: 深入解读 synchronize锁!

synchronize 是常用的锁,我们知道它可是用在方法上、代码块上,用法简单,但深入起来可要大费周章,下面跟着我深入一下吧。

synchronize 影响线程状态

在 Thread.Sate.class 源码中, 线程被定义为六种状态,(也可以更细分为七种, 详见我的另一篇博文 JUC源码讲解:线程状态转换)

线程进入 sync 时, 会进入 BLOCKED 状态, 而 local() 锁不会让线程进入 BLOCKED 状态, 这是一个简单的区别

退出 sync 时, 线程会进入 RUNNABLE 状态, 这个状态又可细分

RUNNABLE: 线程正在运行, 细分的两个状态会互相转换

  • RUNNING: 线程拿到了CPU时间片, 使用yield() 或没有拿到CPU时间片会进入 READY状态
  • READY: 线程没有拿到CPU时间片, 进入等待队列, 获取到时间片后会进入 RUNNING 状态

这就是 sync 对线程状态的影响了

synchronize 的三种使用方法

sync 有三种使用方式

  • 在普通方法上
  • 在静态方法上
  • 在代码块上

那么,这样使用的时候,sync 是用什么作为锁的呢?


sync 可以使用在普通方法上:

public synchronized void syncMethod() {}

这时候,要想使用普通方法一定要new一个对象,它的锁是new的那个对象的锁

例如 new Main().syncMethod(), 它使用的锁就是 new Main() 这个对象


sync 可以使用在静态方法上:

public static synchronized void staticSyncMethod() {}

它使用的锁, 是 类.class , 这个 class 在 jvm 中是唯一的

sync 可以使用在代码块上

public void method() {
    synchronized (this) {

    }
}

它锁是我们手动指定的

接下来,我们看看 jvm 里对于 sync锁是怎么标记的吧.

在资源管理器中找到我们写的这个java文件,使用 javap -v xxx.java 命令看看

像我这样,我是把结果输出到一个文件里了

javap -v Main.class >> ./1.txt

我先把输出贴出来,然后再大概讲解一下:

Classfile /D:/Code/Java/netty-study/SY-IM/target/classes/yihe/de/yy/Main.class
  Last modified 2024年3月14日; size 683 bytes
  SHA-256 checksum f32532942a1c25993483b3489e6400c45fa0e7922e391ab9251edbc7342637a6
  Compiled from "Main.java"
public class yihe.de.yy.Main
  minor version: 0
  major version: 51
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #2                          // yihe/de/yy/Main
  super_class: #3                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 5, attributes: 1
Constant pool:
   #1 = Methodref          #3.#24         // java/lang/Object."<init>":()V
   #2 = Class              #25            // yihe/de/yy/Main
   #3 = Class              #26            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               LocalVariableTable
   #9 = Utf8               this
  #10 = Utf8               Lyihe/de/yy/Main;
  #11 = Utf8               syncMethod
  #12 = Utf8               staticSyncMethod
  #13 = Utf8               method
  #14 = Utf8               StackMapTable
  #15 = Class              #25            // yihe/de/yy/Main
  #16 = Class              #26            // java/lang/Object
  #17 = Class              #27            // java/lang/Throwable
  #18 = Utf8               main
  #19 = Utf8               ([Ljava/lang/String;)V
  #20 = Utf8               args
  #21 = Utf8               [Ljava/lang/String;
  #22 = Utf8               SourceFile
  #23 = Utf8               Main.java
  #24 = NameAndType        #4:#5          // "<init>":()V
  #25 = Utf8               yihe/de/yy/Main
  #26 = Utf8               java/lang/Object
  #27 = Utf8               java/lang/Throwable
{
  public yihe.de.yy.Main();
    descriptor: ()V
    flags: (0x0001) 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 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lyihe/de/yy/Main;

  public synchronized void syncMethod();
    descriptor: ()V
    flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lyihe/de/yy/Main;

  public static synchronized void staticSyncMethod();
    descriptor: ()V
    flags: (0x0029) ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=0, args_size=0
         0: return
      LineNumberTable:
        line 15: 0

  public void method();
    descriptor: ()V
    flags: (0x0001) 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 18: 0
        line 20: 4
        line 21: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  this   Lyihe/de/yy/Main;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 9
          locals = [ class yihe/de/yy/Main, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 26: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  args   [Ljava/lang/String;
}
SourceFile: "Main.java"

先看看普通方法 syncMethod() 在 jvm 中是怎么标记 sync 的:

再看看静态方法 staticSyncMethod() 在jvm 中是怎么标记 sync 的:

和普通方法差不多

再看看在代码块中是怎么标记 sync 的:

找到 sync 所在的 method() 方法,我们看!

使用 monitorenter 标记 sync 的入口, monitorexit 标记 sync 的出口.

有两个 monitorexit , 是为了防止在发生异常后不能释放锁

这就是对sync三种用法的说明了

synchronize 的四种特性

sync有三种特性, 我做一下讲解:

有序性: 读读, 读写, 写读, 写写 互斥

  • sync 让一个线程对其他线程完全互斥,以保障线程间的有序性

可见性: sync 的可见性体现在两个方面

  • 锁状态对其他线程可见: 上一节提到过, 在 jvm 中,通过标记 ACC_SYNCHRONIZE, monitorenter, monitorexit 来保证锁状态对其他线程是可见的
  • 变量的可见, sync 在释放锁之前会将变量的修改刷新到共享内存中, 是可见的

原子性: 因为对其他线程的完全互斥, 保障了原子性

可重入性: sync 可以嵌套

synchronize 的 Mark Word 锁标记

synchronize 有好几种状态, 在 32位虚拟机中是这样标记的

  • 无锁状态: 锁标志位 01, 是否为偏向锁 0
  • 偏向锁: 锁标志位 01(和无锁状态相同), 是否为偏向锁 1
    • 偏向锁里会存储偏向线程的id
  • 轻量级锁: 锁标志位 00
  • 重量级锁: 锁标志位 10
  • GC标记: 锁标志位 11 (要被GC回收了,会有这个标记)

如图: 着重看 "1bit是否为偏向锁" 和 "2bit锁标志位" 两个参数就好

看偏向锁这一栏, 没有地方存指针,而是存了 线程ID 和 Epoch , 这一点尤为关键, 意味着偏向锁不能存储对象头的 hashCode, 而其他锁是有地方存的. 也就意味着, 当锁对象被隐式(父类)或显试调用了 hashcode 时, 就不能进入偏向锁! , 之后我会在锁升级的章节中着重介绍

标签:lang,JUC,java,Utf8,sync,synchronize,源码,线程,Main
From: https://www.cnblogs.com/acdongla/p/18090107

相关文章

  • Synchronized的底层实现原理(转载)
    synchronized一.synchronized解读1.1简单描述synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized翻译为中文的意思是同步,也称之为同步锁。synchronized的作用是保证在同一时刻,被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的效果。1.2特性......
  • java毕业设计二手物品交易微信小程序[附源码]
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义选题背景:在数字化时代的浪潮下,微信小程序凭借其无需下载安装、即用即走的便捷特性迅速普及,成为互联网应用的新宠。伴随着社会消费观念的变迁和绿色环保意识的提升......
  • 内核源码编译错误及解决方法
    参考资料:https://blog.csdn.net/zhoukaiqili/article/details/126191871https://blog.csdn.net/weixin_42792088/article/details/121657463 1、bc:notfound问题原因:缺少bc命令行工具,bc是一个用于数学计算的命令行工具,它在Linux和Unix系统上广泛使用。bc代表"基本计算器......
  • Spring源码:手写Bean配置
    文章目录一、背景二、解决1、基于xml配置2、基于注解1)定义相关注解2)扫描包下的所有被@Componment修饰的Java类,生成BeanDefinition,注册到BeanFactory三、优化四、总结1、注解2、xml配置一、背景下面的代码,无论是IOC,DI,都需要创建BeanDefinition,设置构造器、初始方......
  • 【附源码】Node.js毕业设计高校后勤管理系统(Express)
    本系统(程序+源码)带文档lw万字以上  文末可获取本课题的源码和程序系统程序文件列表系统的选题背景和意义选题背景:在当今信息化时代,高校后勤管理作为学校日常运营的重要组成部分,承担着保障校园环境、维护学生生活和教学秩序的重要职责。随着教育体系的不断壮大,传统的人工......
  • 【附源码】Node.js毕业设计高校后勤保修系统(Express)
    本系统(程序+源码)带文档lw万字以上  文末可获取本课题的源码和程序系统程序文件列表系统的选题背景和意义选题背景:在当今信息化时代,高效、便捷的管理方式已经成为了各个领域追求的目标。对于高校来说,后勤保修工作是保障校园正常运行的重要环节。传统的高校后勤保修工作主......
  • PHP+MySQL开发组合:智慧同城便民信息小程序源码系统 带完整的安装代码包以及安装部署教
    当前,城市生活的节奏日益加快,人们对各类便民信息的需求也愈发迫切。无论是寻找家政服务、二手交易,还是发布租房、求职信息,一个高效、便捷的信息平台显得尤为重要。传统的信息发布方式往往存在信息更新不及时、查找困难等问题,无法满足现代都市人的需求。罗峰给大家分享一款智慧同......
  • 源码解析丨一次慢SQL排查
    当long_query_time=1时(表info的id为主键),出现下面的慢日志,可能会让你吃惊#Time:2024-01-28T22:52:24.500491+08:00#User@Host:root[root]@[127.0.0.1]Id:8#Query_time:7.760787Lock_time:7.757456Rows_sent:0Rows_examined:0useapple;SETtimestamp=......
  • 870大神安卓Android电影院订票app设计-计算机毕业源码设计
    【友情提示】本店所有安卓Android项目都支持Eclipse和AndroidStudio编程工具,你们可以任意选择开发软件!开发环境:Myclipse(服务器端)+Eclipse(手机客户端)+mysql数据库 影院系统在电影院有着重要的地位,它不仅保存着电影院的基本信息,而且会储存大量的用户个人信息。影......
  • Tomcat源码解析(一)
    一、ApacheTomcat源码环境构建官网:https://tomcat.apache.org/二、Maven环境搭建1、打开IEDA导入项目,然后在项目中创建一个新的pom.xml文件,里面的内容为:<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xs......