1 同步锁synchronized寻根究底

引言提到synchronized,无论是在开发过程中和面试过程中经常遇到的问题synchronized;也算是重灾区了为什么说是重灾区?因为他不像其余的代码,是有源码,能够查看的synchronized是一个关键字。间接是找不到源代码的接下来咱们会通过java内存指令码和c++源码(HotSpot虚拟机源码)给大家分析一下synchronized到底是怎么实现锁同步的

1.1 synchronized场景回顾

指标:

synchronized回顾

概念

synchronized:是Java中的关键字,是一种同步锁。

syn属于哪种锁分类:

  • 乐观锁、乐观锁(syn)
  • 独享锁(syn)、共享锁
  • 偏心锁、非偏心锁(syn)
  • 互斥锁(syn)、读写锁
  • 可重入锁(syn)

tips:

synchronized JDK1.6锁降级 : 无锁 -> 偏差锁 (非锁)-> 轻量级锁 -> 重量级锁(1.6前都是)

多线程个性回顾(面试常问)

原子性:指一个操作或者多个操作,要么全副执行并且执行的过程不会被任何因素打断,要么就都不执行

可见性:是指多个线程拜访一个资源时,该资源的状态、值信息等对于其余线程都是可见的。

有序性:指程序中代码的执行程序 (编译器会重排)

sync能够残缺实现以上三个个性来保障线程安全性,cas就无奈达到原子性。

这是什么原理呢?

1.2 反汇编寻找锁实现原理

指标

通过javap反汇编看一下synchronized到底是怎么加锁的

com.syn.BTest

public class BTest {    private static Object object = new Object();     public synchronized void testMethod() {        System.out.println("Hello World -synchronized method ");    }    public static void main(String[] args) {        synchronized (object) {            System.out.println("Hello World -synchronized block ");                    }    }}

反汇编后,咱们将到什么?

JDK自带的一个工具: javap ,对字节码进行反汇编:

//com.syn.BTest javap -v -c BTest.class

-v:输入附加信息

-c:对代码进行反汇编

反汇编后

解释
被synchronized润饰的代码块,多了两个指令
monitorenter、monitorexit
即JVM应用monitorenter和monitorexit两个指令实现同步

解释

办法调用时会查看办法的 ACC_SYNCHRONIZED 拜访标记是否被设置,如果设置了,执行线程将先获取monitor,获取胜利之后能力执行办法体,办法执行完后再开释monitor。也就是jvm会隐式调用monitorenter和
monitorexit。

  • monitorenter原理

monitorenter首先咱们来看一下JVM标准中对于monitorenter的形容

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.monitorenter
monitorenter :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.monitorexit: The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.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.

翻译如下:

  • monitorenter

每一个对象都会和一个监视器monitor关联。

监视器被占用时会被锁住,其余线程无奈来获取该monitor。

当JVM执行某个线程的某个办法外部的monitorenter时,它会尝试去获取以后对象对应
的monitor的所有权。其过程如下:

  1. 若monior的进入数为0,线程能够进入monitor,并将monitor的进入数置为1。以后线程成为
    monitor的owner(所有者)
  2. 若线程已领有monitor的所有权,容许它重入monitor,则进入monitor的进入数加1
  3. 若其余线程曾经占有monitor的所有权,那么以后尝试获取monitor的所有权的线程会被阻塞,直
    到monitor的进入数变为0,能力从新尝试获取monitor的所有权。
  • monitorexit
  1. 能执行monitorexit指令的线程肯定是领有以后对象的monitor的所有权的线程。
  2. 执行monitorexit时会将monitor的进入数减1。当monitor的进入数减为0时,以后线程退出
    monitor,不再领有monitor的所有权,此时其余被这个monitor阻塞的线程能够尝试去获取这个
    monitor的所有权
    monitorexit开释锁。
    monitorexit插入在办法完结处和异样处,JVM保障每个monitorenter必须有对应的monitorexit。

tips(重要)

简略的了解,monitor就是jvm底层的c++代码中的一个对象ObjectMonitor。

这个对象里有个计数器,来记录以后对象锁有没有人用,用了多少次。

以及一些队列,寄存调度一些须要这把锁的线程。

对于monitor在c++里的构造,咱们下文再具体说。

总结:

1、synchronized是靠ObjectMonitor来管制锁的

2、须要这把锁的线程在monitor的队列里被各种安顿

3、拿到锁的线程被monitor标记,计数加加,开释锁,须要将计数器减减操作

1.3 Monitor详解

指标:Monitor的地位

接下来咱们看它的具体内部结构,以及如何运作的。

1.3.1 Monitor是什么

指标: 通过JVM虚拟机源码剖析synchronized监视器Monitor到底是什么

tips:

c++源码理解即可,原理要明确

面试时很重要,面试过来了就不重要!(瞎说什么大瞎话)

在HotSpot虚拟机中,monitor监视器是由ObjectMonitor实现的。

结构器代码src/share/vm/runtime/objectMonitor.hpp

hpp能够include蕴含cpp的货色,两者都是c++的代码
//结构器ObjectMonitor() {  _header = NULL;  _count = 0;   _waiters = 0,  _recursions = 0; // 线程的重入次数  _object = NULL;   _owner = NULL; // 以后线程,拿到锁的那位  _WaitSet = NULL; // 期待队列,调wait的线程在这里  _WaitSetLock = 0 ;  _Responsible = NULL;  _succ = NULL;  _cxq = NULL; // 竞争队列,挣不到锁先进这里(可自旋)  FreeNext = NULL;  _EntryList = NULL; // 阻塞队列,来自cxq(调unlock时)或者waitSet(调notify时)  _SpinFreq = 0;  _SpinClock = 0;  OwnerIsThread = 0;}

留心这三个列表:

1)cxq(竞争列表)

cxq是一个单向链表。被挂起线程期待从新竞争锁的链表, monitor 通过CAS将包装成ObjectWaiter写入到列表的头部。为了防止插入和取出元素的竞争,所以Owner会从列表尾部取元素。所以这个货色能够了解为一上来竞争没拿到锁的在这里长期待一会(1级缓存)。

2)EntryList(锁候选者列表)

EntryList是一个双向链表。当EntryList为空,cxq不为空,Owener会在unlock时,将cxq中的数据挪动到EntryList。并指定EntryList列表头的第一个线程为OnDeck线程,其余线程就待在外面。所以这个货色能够认为是二次竞争锁还没拿到的(外面有一个马上就会拿到)。(2级缓存)

备注:EntryList跟cxq的区别

在cxq中的队列能够持续自旋期待锁,若达到自旋的阈值仍未获取到锁则会调用park办法挂起。而EntryList中的线程都是被挂起的线程。

3)WaitList

WatiList是Owner线程地调用wait()办法后进入的线程。进入WaitList中的线程在notify()/notifyAll()调用后会被退出到EntryList。

过程总结:

  • 期待锁的线程会待在_cxq和entry set队列中,具体哪个和以后线程取锁的状况无关
  • entry set的表头线程获取到对象的monitor后进入_Owner区域并把monitor中的_owner变量设置为本人,同时monitor中的计数器_count加1
  • 若线程调用wait()办法,将开释以后持有的monitor,_owner变量复原为null,_count自减1,同时该线程进入_WaitSet汇合中期待被唤醒。
  • 若以后线程执行结束也将开释monitor(锁)并复位变量的值,以便其余线程进入获取monitor(锁)。

1.3.2 具体流程图(理解)

monitorenter

monitorenter指令执行地位:

JVM源码:src/share/vm/interpreter/interpreterRuntime.cpp

JVM函数入口:InterpreterRuntime::monitorenter

最终调用:src/share/vm/runtime/objectMonitor.cpp中的 ObjectMonitor::enter

monitorexit

执行monitorexit指令地位:

代码文件:src/share/vm/runtime/objectMonitor.cpp

调用函数:ObjectMonitor::exit

本文由传智教育博学谷 - 狂野架构师教研团队公布,转载请注明出处!

如果本文对您有帮忙,欢送关注和点赞;如果您有任何倡议也可留言评论或私信,您的反对是我保持创作的能源