synchronized原理

synchronized代码块是由一对monitorenter/monitorexit指令实现的,monitor对象是同步的基本实现单元。

    // synchronized(object1)代码块
    public void printA() {
        synchronized (object1) {
            try{
                Thread.sleep(3000);
            }catch (Exception e) {
                System.out.println(e.getMessage());
            }

        }
    }

如上面的synchronized代码块,利用javap工具查看生成的class文件信息如下图。当线程执行到monitorenter指令时,将尝试获取线程对应monitor的所有权,即尝试获取对象的锁。

monitorenter/monitorexit指令对
  • 在java6之前,Monitor的实现完全是依靠操作系统内部的互斥锁,因此需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作
  • 现代的(Oracle)JDK中,JVM对此进行了大刀阔斧地改进,提供了三种不同的Monitor实现,也就是三种不同的锁:偏斜锁、轻量级锁和重量级锁。jvm会根据不同的竞争状况自动切换到合适的锁实现,这种切换就是锁的升级、降级。
    synchronized是JVM内部的Intrinsic Lock(内在锁),所以偏斜锁、轻量级锁、重量级锁的代码实现,并不在核心类库部分,而是在JVM的代码中。在介绍三类锁之前,先介绍一个重要的概念:java对象头。

java对象头

synchronized用的锁是存在Java对象头里的,那么什么是Java对象头呢?

java对象头内容

Hotspot虚拟机的对象头包含的内容如上图。Java对象头一般占有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit),但如果对象是数组类型,则使用三个机器码。

  • Mark Word用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键,所以下面将重点阐述Mark Word。
  • Klass Point是是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
  • 如果对象是数组类型,则用一块来记录数组长度,因为JVM虚拟机可以通过Java对象的元数据信息确定Java对象的大小,但是无法从数组的元数据来确认数组的大小。
    Mark Word用于存储对象自身的运行时数据,如HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。。
    考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据,它会根据对象的状态复用自己的存储空间,也就是说,Mark Word会随着程序的运行发生变化,变化状态如下(32位虚拟机):
32位虚拟机Mark Word

64位虚拟机无锁和偏向锁状态下Mark Word如下图所示:

64位虚拟机Mark Word

1. 偏向锁

偏向锁的适应场景是:大部分对象生命周期中最多会被一个线程锁定。使用偏向锁时,JVM会使用CAS操作,在对象头的Mark Word部分设置线程ID,以表示这个对象偏向于当前线程,所以并不涉及整整的互斥锁,从而降低无竞争开销。偏向锁在Java 6和Java 7中是默认启用的,但是它在应用程序启动几秒钟之后才激活。

  • 可以使用JVM参数来关闭延迟
-XX:-BiasedLockingStartupDelay=0

下图展示了偏向锁的获得和撤销流程

偏向锁的获得和撤销流程

1.1 获取锁

(1)检测Mark Word是否为可偏向状态,即是否为偏向锁1,锁标识位为01;
(2)若为可偏向状态,则测试线程ID是否为当前线程ID,如果是,则执行步骤(5),否则执行步骤(3);
(3)如果线程ID不为当前线程ID,则通过CAS操作竞争锁,竞争成功,则将Mark Word的线程ID替换为当前线程ID,否则执行步骤(4);
(4)通过CAS竞争锁失败,证明当前存在多线程竞争情况,当到达全局安全点,获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码块;
(5)执行同步代码块

1.2 释放锁

偏向锁的释放采用了一种只有竞争才会释放锁的机制,线程是不会主动去释放偏向锁,需要等待其他线程来竞争。如果有另外的线程试图锁定某个已经被偏斜过的对象,JVM就需要撤销偏斜锁,并切换到轻量级锁实现。偏向锁的撤销需要等待全局安全点(这个时间点是上没有正在执行的代码)。其步骤如下:
(1)暂停拥有偏向锁的线程,判断锁对象是否还处于被锁定状态;
(2)撤销偏向锁,恢复到无锁状态(01)或者轻量级锁的状态;
偏向锁状态下没有阻塞发生,并且通过减少不必要的CAS操作(与轻量级锁相比)来降低获取锁的成本。但是,由于撤销偏向锁是很重的行为,因此,当实践中需要大量使用并发操作时,往往并不需要偏斜锁。
可以使用JVM参数来关闭偏向锁,当关闭偏向锁后,将默认获取轻量级锁。

-XX:-UseBiasedLocking=false

2.轻量级锁

引入轻量级锁的主要目的是在多数没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗

轻量级锁及膨胀流程图

2.1 获取锁

(1)判断当前对象是否处于无锁状态(hashcode、0、01),若是,则JVM首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced Mark Word);否则执行步骤(3);
(2)JVM利用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,如果成功表示竞争到锁,则将锁标志位变成00(表示此对象处于轻量级锁状态),执行同步操作;如果失败则执行步骤(3);
(3)判断当前对象的Mark Word是否指向当前线程的栈帧,如果是则表示当前线程已经持有当前对象的锁,则直接执行同步代码块;否则只能说明该锁对象已经被其他线程抢占了,这时轻量级锁需要膨胀为重量级锁,锁标志位变成10,后面等待的线程将会进入阻塞状态;

2.2 释放锁

轻量级锁的释放也是通过CAS操作来进行的,主要步骤如下:
(1)取出在获取轻量级锁保存在Displaced Mark Word中的数据;
(2)用CAS操作将取出的数据替换当前对象的Mark Word中,如果成功,则说明释放锁成功,否则执行步骤(3);
(3)如果CAS操作替换失败,说明有其他线程尝试获取该锁,则需要在释放锁的同时需要唤醒被挂起的线程。
轻量级锁情况下没有阻塞发生,但是使用了大量的CAS操作

3. 重量级锁

重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。

三种锁优缺点对比

三种锁优缺点对比

补充:自旋锁

轻量级锁情况下,如果某个线程竞争锁失败,会尝试使用自旋来获取锁。线程不会立马阻塞,而是通过空转的方式(for(;;;))等待锁的释放。
适用场景:自旋锁可以减少线程的阻塞,这对于锁竞争不激烈,且占用锁时间非常短的代码块来说,有较大的性能提升,因为自旋的消耗会小于线程阻塞挂起操作的消耗。
如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,就不适合使用自旋锁了,因为自旋锁在获取锁前一直都是占用cpu做无用功,线程自旋的消耗大于线程阻塞挂起操作的消耗,造成cpu的浪费。

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