前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >彻底理解Java并发:synchronized关键字

彻底理解Java并发:synchronized关键字

作者头像
栗筝i
发布2022-12-01 21:38:45
4960
发布2022-12-01 21:38:45
举报
文章被收录于专栏:迁移内容

本篇内容包括:Synchronized 关键字简介、synchronized 的修饰对象、对象的内存布局(64位)、Synchronized 锁升级过程等内容。

一、Synchronized 关键字简介

Synchronize 翻译成中文:同步,使同步。synchronized:已同步。synchronized 能够保证同一时刻最多只有一个线程执行该段代码,以达到并发安全的效果。也就是说 Synchronized 在某个线程将资源锁住了之后,其他线程只有在当前线程使用完成后,才可以接着使用。

synchronized 是 Java 中解决并发问题的一种最常用也最简单的一种方法。synchronized 的作用主要有三个:

  1. 原子性:确保线程互斥的访问同步代码;
  2. 可见性:保证共享变量的修改能够及时可见(其实是通过 Java 内存模型中的 “对一个变量 unlock 操作之前,必须要同步到主内存中;如果对一个变量进行 lock 操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中 load 操作或 assign 操作初始化变量值” 来保证的)
  3. 有序性:有效解决重排序问题,即 “一个 unlock 操作先行发生(happen-before)于后面对同一个锁的 lock 操作”。

二、synchronized 的修饰对象

synchronized 可以修饰普通方法,静态方法和代码块。 当synchronized修饰一个方法或者一个代码块的时候,它能够保证在同一时刻最多只有一个线程执行该段代码。

1、synchronized 修饰静态方法,当前类的 Class 对象(类)

我们知道,静态方法实际不属于类的任何一个对象实例,它是直属于“类”的。如果在静态方法上加上 Synchronized 关键字,那么它锁住的就是这个类

2、synchronized 修饰实例方法,锁是当前实例对象(对象)

普通方法并不是这个类独有的:创建多少个对象实例,这个方法就会有多少个。那么当 Synchronized 关键字修饰类的普通方法时,它锁住的就是这个类的对象实例

3、synchronized 修饰代码块,锁是 Synchonized 括号里配置的对象

同步代码块锁主要是对代码块进行加锁,此时同一时刻只能有一个线程获取到该资源,要注意每一把锁只负责当前的代码块,其他的代码块不管


三、对象的内存布局(64位)

因为 Synchronized 都是对对象进行加锁,那我们在了解它的底层实现原理之前,应该了解一下Java对象在内存中的布局,这样比较有利于我们理解,对于一个普通对象来说,它分为四个部分:

  • mark-word:是整个对象内存布局的重中之重,因为它里面存储了很多非常重要的信息,它共占8个字节,存储了对象的hashcode**、锁信息、**分代年龄和GC标志信息,它与class pointer共同称之为对象头;
  • class pointer:存储的是该对象的class文件地址,换句话说,就是可以通过 class pointer 知道,这是哪个类的对象;
  • instance data:存放的一般是类中的变量数;
  • padding data:不一定会用到,它最主要的作用就是保证整个对象所占的字节可以被8字节整除,这样做的目的其实是为了提高读取的效率。

四、Synchronized 锁升级过程

synchronized锁有四种状态,无锁,偏向锁,轻量级锁,重量级锁。锁可以升级但不能降级,但是偏向锁状态可以被重置为无锁状态

jdk1.6之前都是重量级锁,大多数时候是不存在锁竞争的,如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入锁升级。

1、偏向锁

(线程1)获取锁对象时,会在 Java 对象头和栈帧中记录偏向的锁的 ThreadID,下一次,线程获取该锁时会比较 ThreadID 是否一致:

  • 一致(线程1) --> 直接进入而无需使用 CAS 来加锁、解锁;
  • 不一致(线程2) --> 检查对象的 ThreadID 线程是否还存活:
    • 如果存活 --> 代表该对象被多个线程竞争,于是升级成轻量级锁;
    • 否则不存活 --> 将锁重置为无锁状态,锁头重新标记线程为新的 ThreadID(抢占偏向锁失败的线程会触发锁膨胀至轻量级锁)。

如果线程1和线程2的执行时间刚好错开,那么锁只会在偏向锁之间切换而不会升级为轻量级锁,在使用Synchronized的情况下避开了获取锁的成本,所以效率和无锁状态非常接近

2、轻量级锁

对象被多个线程竞争(或关闭偏向锁功能)时,锁由偏向锁升级为轻量级锁,其他线程会通过 CAS + 自旋 的形式尝试获取锁(JDK 1.7 之前是普通自旋,会设定一个最大的自旋次数,默认是 10 次,超过这个阈值就停止自旋。JDK 1.7 之后,引入了适应性自旋。简单来说就是:这次自旋获取到锁了,自旋的次数就会增加;这次自旋没拿到锁,自旋的次数就会减少。)

  1. 膨胀成轻量级锁以后,如果后续的线程是在持有锁的线程执行结束后来抢锁,拿到的依然是轻量级锁。因为释放轻量级锁会恢复成无锁。
  2. 膨胀成轻量级锁以后,如果后续的线程是在持有锁的线程执行结束前来抢锁,就会触发膨胀成重量级。

轻量级锁获取过程:

在代码进入同步块的时候,如果同步对象锁状态为无锁状态,轻量级锁会构造一个Lock Record锁记录,用于存储锁对象目前的 Mark-Word 的拷贝

Lock Record是线程私有的数据结构,每一个线程都有一个可用Lock Record列表,同时还有一个全局的可用列表。每一个被锁住的对象Mark Word都会和一个Lock Record关联(对象头的MarkWord中的Lock Word指向Lock Record的起始地址),同时Lock Record中有一个Owner字段存放拥有该锁的线程的唯一标识(或者object mark word),表示该锁被这个线程占用。

拷贝成功后,虚拟机将使用 CAS 尝试将对象头的 MarkWord 的 Lock-Word(锁记录指针) 指向当前线程 Lock Record 的起始地址,并将 Lock Record 的 owner 指向对象的 Mark-Word:

  • 如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象 Mark-Word 的锁标志位状态设置为 00,表示此对象处于轻量级锁定状态。
  • 如果这个更新动作失败了,虚拟机首先会检查对象的 Lock-Word(锁记录指针) 是否指向当前线程的 Lock Record,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明多个线程竞争锁,锁升级为重量级。
3、重量级锁

当线程的自旋后依然没获取到锁或者判定多个线程竞争锁时,为避免CPU无端耗费,锁由轻量级锁升级为重量级锁。

升级为重量级锁时,锁标志的状态值变为 10,此时 MarkWord 的 Lock-Word 指向重量级锁的指针,获取锁的同时会阻塞其他正在竞争该锁的线程,依赖对象内部的监视器(monitor)实现,monitor 又依赖操作系统底层,需要从用户态切换到内核态,成本非常高。

Synchronized 中对 monitor 锁的实现有用到两个指令: monitorenter 和 monitorexit (可通过 javap -verbose XXX.class 反汇编查看)。 Synchronized 在 JVM 里的实现都是 基于进入和退出 Monitor 对象来实现方法同步和代码块同步,虽然具体实现细节不一样,但是都可以通过成对的 MonitorEnter 和 MonitorExit 指令来实现,可以把执行 monitorenter 理解为加锁,执行 monitorexit 理解为释放锁,每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为 0。 monitorenter:执行 monitorenter 的线程尝试获得 monitor 的所有权,会发生以下这三种情况之一:

  1. 如果该 monitor 的计数为 0,则线程获得该 monitor 并将其计数设置为 1。然后,该线程就是这个 monitor 的所有者。
  2. 如果线程已经拥有了这个 monitor ,则它将重新进入,并且累加计数。
  3. 如果其他线程已经拥有了这个 monitor,那个这个线程就会被阻塞,直到这个 monitor 的计数变成为 0,代表这个 monitor 已经被释放了,于是当前这个线程就会再次尝试获取这个 monitor。

monitorexit:monitorexit 的作用是将 monitor 的计数器减 1,直到减为 0 为止。代表这个 monitor 已经被释放了,已经没有任何线程拥有它了,也就代表着解锁,所以,其他正在等待这个 monitor 的线程,此时便可以再次尝试获取这个 monitor 的所有权 再底层的,monitor又依赖操作系统的 MutexLock(互斥锁)来实现的,所以重量级锁也被称为互斥锁。(Mutex 在 Windows 和 Linux中的实现有着显著的区别)

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-11-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Synchronized 关键字简介
  • 二、synchronized 的修饰对象
    • 1、synchronized 修饰静态方法,当前类的 Class 对象(类)
      • 2、synchronized 修饰实例方法,锁是当前实例对象(对象)
        • 3、synchronized 修饰代码块,锁是 Synchonized 括号里配置的对象
        • 三、对象的内存布局(64位)
        • 四、Synchronized 锁升级过程
          • 1、偏向锁
            • 2、轻量级锁
              • 3、重量级锁
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档