IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java基础-Synchronized关键字 -> 正文阅读

[Java知识库]Java基础-Synchronized关键字

概述

Synchronized是Java中保证线程安全的使用较多的一个关键字

使用场景

  1. 修饰实例方法,对当前实例对象this加锁
    public class Synchronized {
        public synchronized void aaa(){
    
        }
    }
    
  2. 修饰静态方法,对当前类的Class对象加锁
    public class Synchronized {
        public synchronized static aaa(){
        }
    }
    
  3. 修饰代码块,指定一个加锁的对象,给对象加锁
    public class Synchronized {
        public void aaa(){
            synchronized(new test()){
    
            }
        }
    }
    

其实就是锁方法、锁代码块和锁对象

Java 对象在内存中的结构

  • 对象头
    • Mark Word(标记字段):默认存储对象的HashCode,分代年龄和锁标志位信息。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。
    • Klass Point(类型指针):对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
  • 实例数据
    • 这部分主要是存放类的数据信息,父类的信息。
  • 对齐填充
    • 由于虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐。
    • 一个空对象占多少个字节
      • 就是8个字节,是因为对齐填充的关系,不到8个字节对齐填充会帮我们自动补齐

synchronized 锁使用的就是对象头的 Mark Word 字段中的一部分
Mark Word 中的某些字段发生变化,就可以代表锁不同的状态
如果当前对象是无锁状态,对象的 Mark Word 如图所示:
在这里插入图片描述
该对象头的 Mark Word 字段分为四个部分:

  • 对象的 hashCode ;
  • 对象的分代年龄,这部分用于对对象的垃圾回收;
  • 是否为偏向锁位,1代表是,0代表不是;
  • 锁标志位,这里是 01。

synchronized关键字的实现原理

这里看实现很简单,我写了一个简单的类,分别有锁方法和锁代码块,我们反编译一下字节码文件,就可以了。

public class Synchronized {
    public synchronized void husband(){
        synchronized(new Volatile()){

        }
    }
}

编译完成,我们去对应目录执行 javap -c xxx.class 命令查看反编译的文件

public class juc.Synchronized
  minor version: 0
  major version: 49
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#14         // java/lang/Object."<init>":()V
   #2 = Class              #15            // juc/Synchronized
   #3 = Class              #16            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               LocalVariableTable
   #9 = Utf8               this
  #10 = Utf8               Ljuc/Synchronized;
  #11 = Utf8               husband
  #12 = Utf8               SourceFile
  #13 = Utf8               Synchronized.java
  #14 = NameAndType        #4:#5          // "<init>":()V
  #15 = Utf8               juc/Synchronized
  #16 = Utf8               java/lang/Object
{
  public juc.Synchronized();
    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
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljuc/Synchronized;

  public synchronized void husband();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED  // 这里
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                  // class juc/Synchronized
         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
      LineNumberTable:
        line 10: 0
        line 12: 5
        line 13: 15
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  this   Ljuc/Synchronized;
}

同步方法
编译器会为其自动生成了一个 ACC_SYNCHRONIZED 关键字用来标识。同步方法的时候,一旦执行到这个方法,就会先判断是否有标志位,然后,ACC_SYNCHRONIZED会去隐式调用刚才的两个指令:monitorenter和monitorexit。所以归根究底,还是monitor对象的争夺。

同步代码块
当我们进入一个方法的时候,执行monitorenter,就会获取当前对象的一个所有权,这个时候monitor进入数为1,当前的这个线程就是这个monitor的owner
如果你已经是这个monitor的owner了,你再次进入,就会把进入数+1.
同理,当他执行完monitorexit,对应的进入数就-1,直到为0,才可以被其他线程持有。

为什么会有两个 monitorexit 指令呢
正常退出,得用一个 monitorexit 吧,如果中间出现异常,锁会一直无法释放。所以编译器会为同步代码块添加了一个隐式的 try-finally 异常处理,在 finally 中会调用 monitorexit 命令最终释放锁。

优化锁升级

JDK1.6之前Synchronized是重量级锁, 是ObjectMonitor调用的过程
ObjectMonitor源码中Atomic::cmpxchg_ptr,Atomic::inc_ptr等内核函数,对应的线程就是park()和upark(),这个操作涉及用户态和内核态的转换了,这种切换是很耗资源的,所以效率才低

JDK1.6之后对Synchronized做了优化
在这里插入图片描述
升级方向:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
注意:这个升级过程是不可逆的

偏向锁
偏向锁的申请流程
在这里插入图片描述

  • 首先需要判断对象的 Mark Word 是否属于偏向模式,如果不属于,那就进入轻量级锁判断逻辑。否则继续下一步判断;
  • 判断目前请求锁的线程 ID 是否和偏向锁本身记录的线程 ID 一致。如果一致,继续下一步的判断,如果不一致,跳转到步骤4;
  • 判断是否需要重偏向,重偏向逻辑在后面一节批量重偏向和批量撤销会说明。如果不用的话,直接获得偏向锁;
  • 利用 CAS 算法将对象的 Mark Word 进行更改,使线程 ID 部分换成本线程 ID。如果更换成功,则重偏向完成,获得偏向锁。如果失败,则说明有多线程竞争,升级为轻量级锁。

轻量级锁
在这里插入图片描述
该对象头Mark Word分为两个部分。第一部分是指向栈中的锁记录的指针,第二部分是锁标记位,针对轻量级锁该标记位为 00。

轻量级锁的上锁步骤

  • 如果当前这个对象的锁标志位为 01(即无锁状态或者轻量级锁状态),线程在执行同步块之前,JVM 会先在当前的线程的栈帧中创建一个 Lock Record,包括一个用于存储对象头中的 Mark Word 以及一个指向对象的指针。
  • JVM接下来会利用CAS尝试把对象原本的Mark Word 更新为Lock Record的指针,成功就说明加锁成功,改变锁标志位,执行相关同步操作。
  • 如果失败了,就会判断当前对象的Mark Word是否指向了当前线程的栈帧,是则表示当前的线程已经持有了这个对象的锁,否则说明被其他线程持有了,继续锁升级,修改锁的状态,之后等待的线程也阻塞。

补充

用户态和内核态
Linux系统的体系结构分为用户空间(应用程序的活动空间)和内核。
我们所有的程序都在用户空间运行,进入用户运行状态也就是(用户态),但是很多操作可能涉及内核运行,比我I/O,我们就会进入内核运行状态(内核态)。
在这里插入图片描述

  • 用户态把一些数据放到寄存器,或者创建对应的堆栈,表明需要操作系统提供服务。
  • 用户态执行系统调用(系统调用是操作系统的最小功能单位)。
  • CPU切换到内核态,跳到对应的内存指定的位置执行指令。
  • 系统调用处理器去读取我们先前放到内存的数据参数,执行程序的请求
  • 调用完成,操作系统重置CPU为用户态返回结果,并执行下个指令

synchronized和Lock

  • synchronized是关键字,是JVM层面的底层啥都帮我们做了,而Lock是一个接口,是JDK层面的有丰富的API
  • synchronized会自动释放锁,而Lock必须手动释放锁
  • synchronized是不可中断的,Lock可以中断也可以不中断
  • 通过Lock可以知道线程有没有拿到锁,而synchronized不能
  • synchronized能锁住方法和代码块,而Lock只能锁住代码块
  • synchronized是非公平锁,ReentrantLock可以控制是否是公平锁
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-10 13:17:12  更:2021-08-10 13:17:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/10 5:10:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码