Synchronized原理
2019-02-08 本文已影响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
- 修饰普通方法,相当于锁当前对象,调用者,也指 this对象
- 修饰静态方法,相当于锁当前类对象,也指 Test.class
- 修饰代码块,可以缩小锁的范围,提升性能
注意事项:
- 锁普通方法和锁this的代码块,都是对象锁,锁静态方法和Test.class是类锁。
- 有锁方法和无锁方法互相不影响
- 类锁和对象锁互相不影响
- 释放锁的时候,修改的变量对所有线程可见,满足HB原则,从而实现线程安全
Synchronized3种级别锁原理(JDK1.6后的特性)
特性:
- MarkWord总共有四种状态:无锁状态、偏向锁、轻量级锁和重量级锁。
- 随着锁的竞争:偏向锁-->轻量级锁-->重量级锁
- 锁的升级是单向的,只能从低到高升级
实现原理:
- 每个java对象对应唯一的消息头,锁的状态不同,消息头的结构和标志位不同
- 处于不同状态的锁,不仅仅MarkWord的数据结构不同,同步线程们竞争锁的方式也不同
Markword的由来
对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
Header对象头包括两部分信息:
- MarkWord:用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit
- klass 对象头的另外一部分是klass类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例.
偏向锁的原理
image竞争的逻辑:如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。
- 偏向锁状态的消息头结构: 消息头锁标志位01、是否是偏向锁0 1、偏向线程ID
- 如果3个条件都满足,锁标志位-- 01,是否是偏向锁 --1,偏向线程ID -- 当前线程,则直接获取到锁,不需要任何CAS操作,而且执行完同步代码块后,并不会修改这3个条件
- 如果一个新的线程尝试获取锁(但是未竞争,之前的线程已经使用完了),发现锁标志位-- 01,是否是偏向锁 --1,偏向线程ID --不是自己,则会触发一个check,old线程的锁是否使用完了,如果使用完了,则不存在竞争,CAS把偏向线程ID指向自己,这个对象锁就归自己所有了
- 如果一个新的线程尝试获取锁(竞争态),锁标志位-- 01,是否是偏向锁 --1,偏向线程不是自己,且check发现还在使用,竞争成立,则挂起当前线程,并达到安全点后(old线程执行完了),该对象锁升级为轻量级锁
- 偏向锁的释放采用了一种只有竞争才会释放锁的机制,线程是不会主动去释放偏向锁,需要等待其他线程来竞争
偏向锁的好处
- 如果不存在多线程同时竞争一把锁的时候,减少CAS操作
- 老线程重复使用锁,无需任何CAS操作
- 新线程获取偏向锁,但是没有竞争,只需要在满足条件的时候CAS偏向线程ID即可
- 完美支持重入功能,而且没有任何CAS操作
轻量级锁原理
- check消息头的状态:是否处于无锁状态(偏向锁状态结束了,因为存在竞争,等安全点后释放锁了,就是无锁状态)
- 是的话,所有争夺的线程都会拷贝一份消息头到各自的线程栈的 lock record中,叫做displace Mark Word,并且会记录自己的唯一线程标识符
- CAS 把公有的消息头,变成指向自己线程标识符,这个时候消息头的数据结构发生改变,变成线程引用
- CAS成功的线程会,执行同步操作
- 最后把displace Mark Word 写回到 公有消息头里面,释放锁
- 重入的时候,无需要任何的操作,只需要在自己的displace Mark Word中标记一下
- CAS争夺锁失败的线程会发生自旋,自旋一定次数后还是失败的话,会修改消息头的状态为重量级锁,并且自身进入阻塞状态,等待拥有锁的线程执行结束。
轻量级锁的优势:在获取锁的耗时不长的时候(比如锁的执行时间短、或者争抢的线程不多可以很快获得锁),通过一定次数的自旋,避免了重量级锁的线程阻塞和切换,提升了响应速度也兼顾了CPU的性能。
Synchronized 重量级锁的实现原理
原理:竞争线程存在EntryList里面阻塞等待,当锁释放时,EntryList所有线程被唤醒,做一次非公平的CAS竞争
- 所有的竞争线程首先通过CAS拼接到Contention List这个队列里面,所以Contention List的CAS操作会很频繁
- 当Owner unlock的时候,会把一些线程推入到EntryList当中,然后EntryList开始CAS竞争Owner(非公平锁),竞争成功就拿到锁,其它线程开始阻塞,等待下一次机会
- 当一个有锁线程调用obj.wait方法时,它会放弃Owner,进入WaitSet,当调用notify方法,会从waitSet中随机选一个线程,nitifyAll就是全部进行操作,让他们进入EntryList
重量级锁(Synchronized的对象锁)monitor的结构:
- Owner:如果为NULL,表示该对象处于非锁定状态,或者指向拥有该锁的线程
- Count:哪些调用wait方法被阻塞的线程被放置在这里;
- WaitSet:记录处于wait状态的线程
- Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;
- EntryList:Contention List中那些有资格成为候选资源的线程被移动到Entry List中;
其它特点:
- 处于ContentionList、EntryList、WaitSet中的线程都处于阻塞状态,该阻塞是由操作系统来完成的
- Synchronized是非公平锁(类似RentrenLock)。Synchronized在线程进入ContentionList时,等待的线程会先尝试自旋获取锁,如果获取不到就进入ContentionList,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占OnDeck线程的锁资源。
3种锁竞争的对比
- 没有竞争的时候选择偏向锁,极少的CAS开销,线程重入时甚至没有CAS的开销,甚至比ReentrantLock的实现更轻量
- 轻微竞争的时候,自旋的性能最好,少数几个争抢的线程通过自旋获得锁,Cpu的开销不大,获取锁的速度又极快,业务也不复杂
- 竞争激烈的时候,大量竞争线程的自旋会导致Cpu的无意义消耗,所以在EntryList中阻塞,等待释放锁的唤醒竞争是最好的方案。类似于ReentrantLock的AQS队列的方案。