美文网首页
Synchronized原理

Synchronized原理

作者: 黄靠谱 | 来源:发表于2019-02-08 20:43 被阅读51次

    参考资料

    锁原理
    https://www.jianshu.com/p/8ce9c0ed428d

    https://blog.csdn.net/javazejian/article/details/72828483

    死磕java
    http://cmsblogs.com/?p=2071

    概述

    Synchronized,它就是一个:非公平,悲观,独享,互斥,可重入锁。
    优化以后,是基于JVM内部实现,可以根据竞争激烈程度,从偏向锁-->轻量级锁-->重量级锁升级。

    synchronized的用法

    • 修饰方法和修饰代码块
    • 锁普通对象和锁类对象和锁this
    1. 修饰普通方法,相当于锁当前对象,调用者,也指 this对象
    2. 修饰静态方法,相当于锁当前类对象,也指 Test.class
    3. 修饰代码块,可以缩小锁的范围,提升性能

    注意事项:

    1. 锁普通方法和锁this的代码块,都是对象锁,锁静态方法和Test.class是类锁。
    2. 有锁方法和无锁方法互相不影响
    3. 类锁和对象锁互相不影响
    4. 释放锁的时候,修改的变量对所有线程可见,满足HB原则,从而实现线程安全

    Synchronized3种级别锁原理(JDK1.6后的特性)

    特性:

    1. MarkWord总共有四种状态:无锁状态、偏向锁、轻量级锁和重量级锁。
    2. 随着锁的竞争:偏向锁-->轻量级锁-->重量级锁
    3. 锁的升级是单向的,只能从低到高升级

    实现原理:

    • 每个java对象对应唯一的消息头,锁的状态不同,消息头的结构和标志位不同
    • 处于不同状态的锁,不仅仅MarkWord的数据结构不同,同步线程们竞争锁的方式也不同

    Markword的由来

    对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)

    Header对象头包括两部分信息:

    1. MarkWord:用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit
    2. klass 对象头的另外一部分是klass类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例.

    偏向锁的原理

    image

    竞争的逻辑:如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。

    1. 偏向锁状态的消息头结构: 消息头锁标志位01、是否是偏向锁0 1、偏向线程ID
    2. 如果3个条件都满足,锁标志位-- 01,是否是偏向锁 --1,偏向线程ID -- 当前线程,则直接获取到锁,不需要任何CAS操作,而且执行完同步代码块后,并不会修改这3个条件
    3. 如果一个新的线程尝试获取锁(但是未竞争,之前的线程已经使用完了),发现锁标志位-- 01,是否是偏向锁 --1,偏向线程ID --不是自己,则会触发一个check,old线程的锁是否使用完了,如果使用完了,则不存在竞争,CAS把偏向线程ID指向自己,这个对象锁就归自己所有了
    4. 如果一个新的线程尝试获取锁(竞争态),锁标志位-- 01,是否是偏向锁 --1,偏向线程不是自己,且check发现还在使用,竞争成立,则挂起当前线程,并达到安全点后(old线程执行完了),该对象锁升级为轻量级锁
    5. 偏向锁的释放采用了一种只有竞争才会释放锁的机制,线程是不会主动去释放偏向锁,需要等待其他线程来竞争

    偏向锁的好处

    1. 如果不存在多线程同时竞争一把锁的时候,减少CAS操作
    • 老线程重复使用锁,无需任何CAS操作
    • 新线程获取偏向锁,但是没有竞争,只需要在满足条件的时候CAS偏向线程ID即可
    1. 完美支持重入功能,而且没有任何CAS操作

    轻量级锁原理

    1. check消息头的状态:是否处于无锁状态(偏向锁状态结束了,因为存在竞争,等安全点后释放锁了,就是无锁状态)
    2. 是的话,所有争夺的线程都会拷贝一份消息头到各自的线程栈的 lock record中,叫做displace Mark Word,并且会记录自己的唯一线程标识符
    3. CAS 把公有的消息头,变成指向自己线程标识符,这个时候消息头的数据结构发生改变,变成线程引用
    4. CAS成功的线程会,执行同步操作
    5. 最后把displace Mark Word 写回到 公有消息头里面,释放锁
    6. 重入的时候,无需要任何的操作,只需要在自己的displace Mark Word中标记一下
    7. CAS争夺锁失败的线程会发生自旋,自旋一定次数后还是失败的话,会修改消息头的状态为重量级锁,并且自身进入阻塞状态,等待拥有锁的线程执行结束。

    轻量级锁的优势:在获取锁的耗时不长的时候(比如锁的执行时间短、或者争抢的线程不多可以很快获得锁),通过一定次数的自旋,避免了重量级锁的线程阻塞和切换,提升了响应速度也兼顾了CPU的性能。

    Synchronized 重量级锁的实现原理

    原理:竞争线程存在EntryList里面阻塞等待,当锁释放时,EntryList所有线程被唤醒,做一次非公平的CAS竞争

    1. 所有的竞争线程首先通过CAS拼接到Contention List这个队列里面,所以Contention List的CAS操作会很频繁
    2. 当Owner unlock的时候,会把一些线程推入到EntryList当中,然后EntryList开始CAS竞争Owner(非公平锁),竞争成功就拿到锁,其它线程开始阻塞,等待下一次机会
    3. 当一个有锁线程调用obj.wait方法时,它会放弃Owner,进入WaitSet,当调用notify方法,会从waitSet中随机选一个线程,nitifyAll就是全部进行操作,让他们进入EntryList

    重量级锁(Synchronized的对象锁)monitor的结构:

    • Owner:如果为NULL,表示该对象处于非锁定状态,或者指向拥有该锁的线程
    • Count:哪些调用wait方法被阻塞的线程被放置在这里;
    • WaitSet:记录处于wait状态的线程
    • Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;
    • EntryList:Contention List中那些有资格成为候选资源的线程被移动到Entry List中;

    其它特点:

    1. 处于ContentionList、EntryList、WaitSet中的线程都处于阻塞状态,该阻塞是由操作系统来完成的
    2. Synchronized是非公平锁(类似RentrenLock)。Synchronized在线程进入ContentionList时,等待的线程会先尝试自旋获取锁,如果获取不到就进入ContentionList,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占OnDeck线程的锁资源。

    3种锁竞争的对比

    1. 没有竞争的时候选择偏向锁,极少的CAS开销,线程重入时甚至没有CAS的开销,甚至比ReentrantLock的实现更轻量
    2. 轻微竞争的时候,自旋的性能最好,少数几个争抢的线程通过自旋获得锁,Cpu的开销不大,获取锁的速度又极快,业务也不复杂
    3. 竞争激烈的时候,大量竞争线程的自旋会导致Cpu的无意义消耗,所以在EntryList中阻塞,等待释放锁的唤醒竞争是最好的方案。类似于ReentrantLock的AQS队列的方案。

    相关文章

      网友评论

          本文标题:Synchronized原理

          本文链接:https://www.haomeiwen.com/subject/oxmosqtx.html