synchronized锁机制的实现原理

Synchronized 锁机制的实现原理

Synchronized是Java种用于进行同步的关键字,synchronized的底层使用的是锁机制实现的同步。在Java中的每一个对象都可以作为锁。

Java中synchronized的两个特性:

  1. 互斥性:即在同一时间内只允许同一个县城持有某一个对象锁,通过这种特性来实现多个线程中的协调机制,这样在同一时间内只有一个线程对同步的代码进行访问,互斥性往往也被称为原子性。

  2. 可见性:必须确保在获取锁的时候,线程内共享变量的值和主存一致,并且也必须保证在锁在被释放前,对共享变量所做的修改,对于随后获取锁的另一个线程是可见的(即在获取锁时应该获得的是最新的共享变量的值),否则另一个线程可能在本地缓存的某一个副本上继续操作从而导致结果不一致。

synchronized锁具体的三种形式:

  1. 对于普通同步方法,锁对象是当前实例对象,进入同步代码块前需要获得当前对象的锁。
  2. 对于静态同步方法,锁的是当前类的对象,在Java中每一个类都有一个Class对象。
  3. 对于同步方法块,锁的是synchronized圆括号内的对象,这里的对象可以是一个普通的对象,也可以是一个Class对象,如果是Class对象的话,也就是所谓的类锁了,而类锁是通过类的Class对象实现的。

synchronized的原理

JVM基于进入和推出Monitor对象来实现同步方法和同步代码块,但两者的实现细节不同。

  • 同步代码块是使用monitorentermonitorexit指令实现的。
  • synchronized修饰的方法并没有monitorentermonitorexit指令,而取代之的是ACC_SYNCHRONIZED标识,该标志指明了该方法是一个同步方法,从而执行相应的同步调用。

monitorenter指令实现编译后茶后到同步代码块开始的位置,而monitorexit是插入在方法结束出和异常处,JVM要保证每个monitorenter必须要有一个与之相对应的monitorexit。**任何一个对象都可以和一个monitor相关联,且当一个执行monitorenter指令的时候,会尝试获取synchronized圆括号中对象中相关联的monitor的所有权,即尝试获取这个对象的锁(这里的是重量级锁,而没有后面提到的偏向锁和轻量级锁)。

下面来看一个具体的例子:

public class SynchronizedTest {
    
    public void readFile() throws IOException {
        synchronized(this) {
            System.out.println("同步代码块");
        }
    }
}

经过javap反编译后,结果如下:

[图片上传失败...(image-96303b-1550504786733)]

可以看出synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

下面的是同步方法:

public class SynchronizedTest {
    public synchronized void readFile() throws IOException {
        System.out.println("同步代码块");
    }
}

反编译后如下图所示:

image

Java对象头

在Hotspot虚拟机中,对象头主要包含两部分数据:

  • Mark Word(标记字段)
  • Klass Pointer(类型指针)

MarkWord: 默认存储对象的HashCode,GC分代和锁标志位信息。这些信息都与对象自身定义无关的数据,所以Mark Word被设计成非固定数据结构,以便在极小的空间存储尽可能多的数据。(Mark Word的大小在32位虚拟机中占32个字节,在64位的虚拟机中占64个字节)。也就是说在运行期间Mark Word里面存储的数据会随着锁标志的变化而变化。

Klass Point: 对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。同样的它的大小在32位虚拟机中占32个字节,在64位的虚拟机中占64个字节。

对于不同的对象头它们的总结如下表:

长度 内容 说明
32/64bit MarkWord 存储对象的hashCode,GC分代和锁信息
32/64bit Klass Point 存储到类元数据的指针
32/64bit Array Length 这个只针对数组对象而言,存储数组的长度

Java对象头中的MarkWord里面的存储对象如下表:

锁状态 25bit 4bit 1bit是否偏向锁 2bit锁标志
无锁状态 对象的hashCode 对象分代年龄 0 01

在运行期间,Mark Word里存储的数据会随着锁标志的变化而变化,它的变化如下表所示:

Mark Word的四种变化

在64位的虚拟下,Mark Word是64bit的存储结构,其存储结构如下表:

锁状态 25bit 31bit 1bit 4bit 1bit 2bit
cms_free 分代年龄 偏向锁 锁标志位
无锁 unused hashCode 0 01
偏向锁 ThreadId(54bit) Epoch(2bit) 1 01

对象头的最后两位存储了锁的标志,01是初始状态表示无锁,其对象头里存储的是对象的哈希吗,随着锁级别的不同,对象头中存储的内容也会不同。偏向锁存储的当前占用此对象的线程ID;而轻量级锁则是存储指向线程栈中锁记录的指针。

Monitor监视器锁

其中轻量级锁和偏向锁是Java6对synchronized锁进行优化后增加的,我们稍后会进行分析。这里我们主要分析重量级锁,也就是通常所说的synchronized对象锁,锁标识为10,其中指针指向monitor对象(也称之为管程或者监视器锁)的起始地址。每个对象都存在一个monitor与之关联,对象与其monitor之间也存在着多种实现方式,如monitor可以与对象一起创建或者销毁或当前线程试图获取锁时自动生成,但一个monitor被某线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是有ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的)

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //记录个数
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
}

ObjectMonitor中有两个队列,_WaitSet和_EntryList,用来保存ObjectWaiter对象列表,每个等待锁的线程都会被封装成ObjectWaiter对象,_owner指向指向持有ObjectMonitor对象的线程,当多个线程同时访问同一同步代码块或者同步方法时,首先会进入_EntryList队列,当线程获取到monitor后进入_Owner区域并把monitor中的_owner变量设置为当前线程同时monitor中的计数器count加1,若线程调用wait()方法,将释放当前持有的monitor,_owner变量恢复为null,count自减1,同时该线程进入_WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。如下图所示:

img

所以,monitor对象存在于每一个Java对象的对象头(存储指针的指向),synchronized锁便是通过这种方式获取的,也是为什么Java中任意对象都可以作为锁的原因,同时也是notify/notifyAll/wait方法等存在于顶级对象Object中的原因。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容