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的用法

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

注意事项:

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

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

特性:

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

实现原理:

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操作
  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的结构:

其它特点:

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

3种锁竞争的对比

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

猜你喜欢

热点阅读