编程语言
首页 > 编程语言> > java并发之 Synchronized关键字 详详详解

java并发之 Synchronized关键字 详详详解

作者:互联网

Synchronized概念

Synchronized用法

主要有三种使用方式

synchronized void method() {
  //业务代码
}

以当前实例对象作为锁

synchronized void staic method() {
  //业务代码
}

以当前类的class对象作为锁(这里class对象和实例对象是分开的)

synchronized(this) {
  //业务代码
}

此处是用户自己指定作为锁的对象,当然这里可以指定为当前实例对象即this,就和修饰实例方法很像了;当然也可以指定为类对象,即xxx.class,则此时和修饰静态方法就很像了。

对象为什么可以作为锁

在简单的介绍了Synchronized的概念和用法之后,我们需要一个前铺知识,就是java对象为何可以作为锁,以及如何作为锁。

首先java对象在内存中的结构是这样的:

在这里插入图片描述
分为对象头,实例数据,和对齐填充。
(对象的头部;对象自身的一些数据;对象数据不够,为了满足jvm的标准,从而填充数据进行补齐)

Synchronized用的锁是存在于java对象头里的。
java对象头的结构为三部分:

而mark word的结构如下所示:(这里包括下面统一看32位的了,懒得再看64位的,都差不多)
在这里插入图片描述

总体来说,就是对象头中的markword存储着该对象的锁的相关信息,该对象锁是什么类型什么状态,就看这里的mark word是什么值就ok了。

Synchronized底层实现

(此处针对的是jdk1.6之前的Synchronized与jdk1.6之后的重量级锁的实现,此处意为和偏向锁,轻量锁区分开,它们不使用monitor)
针对代码块和方法,Synchronized的实现方式是不一样的,这里需要区分开。

代码块

  • 如果自己写一个demo,然后用javap查看字节码指令,就可以发现
  • 在同步代码块的前后是有monitorenter和monitorexit指令的,这是jvm帮我我们加的
  • 当执行monitorenter指令时,线程试图获取锁也就是获取对象监视器monitor的持有权
  • 在 Java 虚拟机(HotSpot)中,Monitor 是基于 **C++**实现的,由ObjectMonitor实现的。
  • 当获取到锁时(重量锁),markword中会存储指向monitor对象的指针
    在执行monitorenter时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。
    在执行 monitorexit 指令后,将锁计数器设为 0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
  • (另外,wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。)

总的来说,就是jvm帮我们加了两个指令,让线程去获取C++实现的monitor对象,如果获取到了,则将该对象的指针放到markword中去,从而获取锁完毕(重量锁)

方法

  • 如果自己写一个demo,然后用javap查看字节码指令,就可以发现
  • synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

两者的本质都是对对象监视器 monitor 的获取。本质一样

Synchronized的优化

在jdk1.6之前,Synchronized是一个很沉重的关键字。
早期的Synchronized是一个重量级锁,是一个悲观锁。

因为上面介绍的monitor是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。

而实际上,在我们的应用场景中,并不是每时每刻都需要这么沉重的锁。
很多时候,我们会出现很长时间的,只有一个线程访问同一个同步代码块的情形;也会出现,多个线程交替访问同步块的情形;当然也会出现多个线程同时竞争同步代码块的情形。
针对不同的情形,如果我们都采用单一的重量锁来解决,那么效率也太低了。

所以jdk1.6在JVM 层面对 synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

细谈java中的锁

(jdk1.6之后)
这里也算是Synchronized原理的实现。
在java对象头的markword中定义的锁类型有四种:

为什么分成这状态上面一节已经说过了。

这四种类型,是级别由低到高的,且是单向的,锁只能保级或者升级,不能降级。

在markword中,各种类型的锁是这样定义的
在这里插入图片描述
下面这个表中是不同锁状态下markword中记录的信息。(很关键)。重量锁那里存储的是指向monitor的指针。
在这里插入图片描述

偏向锁

  • 用来解决只有一个线程访问同步块的场景
  • 偏向锁不会主动释放(什么时候释放?有竞争时)
  • 大部分情况,都是同一个线程进入同一块代码块,所以没有必要针对这种情况来获取锁,释放锁
  • 下面来说一下偏向锁的获取与升级流程:
  1. 线程A访问markword中偏向锁的标识是否为1,锁标识位是否为01–来确认可偏向装填
  2. 如果是可偏向状态,则测试线程ID是否指向当前线程A,如果是进入步骤5,否则进入步骤3
  3. 如果线程ID未指向当前线程A,则通过CAS操作竞争锁。如果竞争成功,则将markword中线程设置为当前线程ID,获取偏向锁成功,然后执行步骤5,如果失败,则执行步骤4
  4. 如果CAS获取偏向锁失败,则表示有竞争。此时,说明已经有其他线程B已经获得了偏向锁。(因为偏向锁不会主动释放),所以当前线程A不知道持有偏向锁的线程B到底还在不在运行。于是,先在到达全局安全点时(没有字节码指令执行的时间点),暂停持有偏向锁的线程B。jvm去检查持有偏向锁的线程B是否存活,如果死了,则将锁变为无锁状态,然后重新偏向新的线程;如果活着,则执行拥有偏向锁的栈,当前锁升级成轻量级锁。(撤销偏向锁时会导致STW)

总的来说,只要超过一个存活的线程访问同一个同步区域,一般都会升级成轻量级锁,偏向锁只应付单线程场景

轻量级锁

  • 用来解决多个线程交替访问同步代码块的场景。
  • 例如线程A执行完之后,退出同步区域,然后线程B就进入同步区域,此时就是轻量级锁。
  • 对于这种超过一个线程的交替访问,明显不需要沉重的重量锁,所以使用轻量锁来解决。
  • 下面来说一下轻量级锁的获取与释放流程:
    • 在线程A进入同步块之前,jvm会帮忙把锁对象头的markword的数据(称为displaced
      markword)复制到线程A的栈帧中的锁记录中去
    • 然后线程A尝试用CAS将锁对象头的markword替换为指向自己的锁记录的指针(竞争锁,加锁)
      如果成功,就是获取到了轻量级的锁
      如果失败,则一直自旋尝试CAS(消耗cpu的,不过也只有一次,当持有锁的线程释放锁时,会膨胀为重量级锁。同时,自旋是有阈值的,如果超过这个阈值,就会直接升级为重量锁)
    • 解锁:解锁时,尝试将锁记录中的Displaced mark word复制回对象头(还原之前的状态)
      如果失败,则意味着有竞争,则膨胀为重量级锁
      如果成功,则不用升级(当然也不可能降级)

重量级锁

  • 其实上面的Synchronized一节中已经讲过重量级锁的实现了,是通过指令,monitor对象等实现的
  • markword中存储了指向monitor对象的指针
  • 当线程A持有重量级锁之后,其他锁再访问同步区域时会被阻塞,等线程A释放锁之后才会将它们唤醒,然后它们再去竞争。

除了在偏向锁状态可以回到无锁状态,其他是不能降级的。

补充一个问题:当在有锁状态时,hashcode放到哪里去了呢?
我在看并发这一块的时候,也在考虑这个问题,hashcode不是没有地方放么。
经过查询,我找到了答案:

参考资料

RednaxelaFX 知乎的回答,hashcode去哪里了
Java并发基石——所谓“阻塞”:Object Monitor和AQS(1)
知乎用户 知乎的回答 java锁为什么相互膨胀
java 中的锁 – 偏向锁、轻量级锁、自旋锁、重量级锁
15.多线程编程中锁的4种状态-无锁状态 偏向锁状态 轻量级锁状态 重量级锁状态
javaGuide并发部分

标签:java,Synchronized,对象,markword,线程,详详,偏向
来源: https://blog.csdn.net/qq_34687559/article/details/114164849