synchronized 底层如何实现?啥是锁的升级,降级

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了synchronized 底层如何实现?啥是锁的升级,降级相关的知识,希望对你有一定的参考价值。

参考技术A

synchronized 底层如何实现?什么是锁的升级,降级。

操作系统分为用户态和内核态,应用级别的程序会运行在用户态,不能访问硬件,操作系统内核的程序会运行在内核态,可以直接访问硬件。synchronized 是重量级锁,运行在虚拟机上,而虚拟机是应用级别的程序,运行在用户态,需要通过向操作系统内核程序发出申请,得到反馈获得锁,所以称sychronized为重量级锁。而cas的锁直接运行在用户态,所以称为轻量级锁。

CAS 叫自旋锁或者无锁,是轻量级锁,用于替代synchronized。

CAS的ABA问题可以用版本号解决。可是如果一个线程在比较值相同的情况下,在修改值之前另一个线程有可能提前修改当前值,这怎么避免呢?如下图:

在用c++编写的native方法compareAndSwap中,如果多线程的情况下,会有 lock cmpxchg这条指令来保证线程安全。在底层有多个cpu指向同一条语句(CAS)时,多个cpu通过一条总线通向这条语句,当lock时,会掐断这条总线,直到通向这条语句的cpu执行完成,总线又连上,允许其他cpu操作这条语句。

Markword的前四个字节中记录了synchronized的锁信息。

轻量级锁也称为自旋锁。除了重量级锁,其他锁都是在用户态完成。

锁升级指的是轻量级锁升级成重量级锁。

为什么自旋锁可以完成多线程的安全,为什么在竞争激烈的情况下要升级成重量级锁,因为自旋锁这些线程一直在while循环,消耗cpu的资源,而重量级锁这些线程排成队列,不消耗cpu资源,这里又可以分为公平锁和非公平锁。

偏向锁是默认启动的,但是有4s的延迟。

启动偏向锁为什么要延迟4秒:因为启动偏向锁效率不一定会提升。如果一开始就知道会有很多线程竞争锁,那么就不必打开偏向锁,从而提高效率。

Synchronized的底层实现:

Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。Synchronized的作用主要有三个:

从语法上讲,Synchronized可以把任何一个非null对象作为"锁",在HotSpot JVM实现中, 锁有个专门的名字:对象监视器(Object Monitor)

Synchronized总共有三种用法:

注意,synchronized 内置锁 是一种对象锁(锁的是对象而非引用变量), 作用粒度是对象 ,可以用来实现对临界资源的同步互斥访问,是 可重入 的。其可重入最大的作用是避免死锁 ,如:

子类同步方法调用了父类同步方法,如没有可重入的特性,则会发生死锁;

monitorenter :每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

(1). 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;

(2). 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;

(3). 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权;

monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

monitorexit指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异步退出释放锁;

通过上面两段描述,我们应该能很清楚的看出Synchronized的实现原理, Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

Java多线程系列:深入详解Synchronized同步锁的底层实现

谈到多线程就不得不谈到Synchronized,很多同学只会使用,缺不是很明白整个Synchronized的底层实现原理,这也是面试经常被问到的环节,比如:

  1. synchronized的底层实现原理
  2. synchronized锁与JVM的实现
  3. synchronized锁升级顺序
  4. synchronized锁的优劣势与应用场景

今天主要分享以上内容,详解synchronized的底层实现,多线程相关的可以参考:

Java多线程系列教程:线程的五大状态,以及线程之间的通信与协作 

Java多线程系列教程:Java线程池的使用方式,核心运行原理、以及注意事项

最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁 

Java多线程系列教程:4种常用Java线程锁的特点,性能比较、使用场景

史上最强多线程面试44题和答案:线程锁+线程池+线程同步等

技术图片

Synchronized

synchronized 翻译为中文的意思是同步,也称之为”同步锁“。

synchronized的作用是保证在同一时刻, 被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的效果。

Synchronized的使用

1.synchronized的3种使用方式

  • 修饰实例方法:作用于当前实例加锁
  • 修饰静态方法:作用于当前类对象加锁
  • 修饰代码块:指定加锁对象,对给定对象加锁

2.synchronized的代码范例

技术图片

Synchronized的底层实现


synchronized的底层实现是完全依赖与JVM虚拟机的。

所以谈synchronized的底层实现,就不得不谈数据在JVM内存的存储:Java对象头,以及Monitor对象监视器。


1.Java对象头


在JVM虚拟机中,对象在内存中的存储布局,可以分为三个区域:

  • 对象头(Header)
  • 实例数据(Instance Data)
  • 对齐填充(Padding)


Java对象头主要包括两部分数据:

  • 类型指针(Klass Pointer:是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例;
  • 标记字段(Mark Word:用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等,它是实现轻量级锁和偏向锁的关键.


2.Java锁对象存储位置


所以,很明显synchronized使用的锁对象是存储在Java对象头里的标记字段里。

技术图片


3.Monitor

synchronized的对象锁,其指针指向的是一个monitor对象(由C++实现)的起始地址。每个对象实例都会有一个 monitor。

Monitor描述为对象监视器,可以类比为一个特殊的房间,这个房间中有一些被保护的数据,Monitor保证每次只能有一个线程能进入这个房间进行访问被保护的数据,进入房间即为持有Monitor,退出房间即为释放Monitor。

使用syncrhoized加锁的同步代码块在字节码引擎中执行时,主要就是通过锁对象的monitor的取用与释放来实现的。

4.线程状态流转在Monitor上体现

描述为对象监视器,当多个线程同时请求某个对象监视器时,对象监视器会设置几种状态用来区分请求的线程:

  • Contention List:所有请求锁的线程将被首先放置到该竞争队列
  • Entry List:Contention List中那些有资格成为候选人的线程被移到Entry List
  • Wait Set:那些调用wait方法被阻塞的线程被放置到Wait Set
  • OnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为OnDeck
  • Owner:获得锁的线程称为Owner
  • !Owner:释放锁的线程

下图反映了个状态转换关系

技术图片



Synchronized 锁的升级顺序

锁解决了数据的安全性,但是同样带来了性能的下降。hotspot 虚拟机的作者经过调查发现,大部分情况下,加锁的代码不仅仅不存在多线程竞争,而且总是由同一个线程多次获得。所以基于这样一个概率。


synchronized 在JDK1.6 之后做了一些优化,为了减少获得锁和释放锁来的性能开销,引入了偏向锁、轻量级锁、自旋锁、重量级锁,锁的状态根据竞争激烈的程度从低到高不断升级。

技术图片


1.偏向锁

偏向锁是JDK6中引入的一项锁优化,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。

偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。


2.轻量级锁

如果明显存在其它线程申请锁,那么偏向锁将很快升级为轻量级锁。


3.自旋锁

自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。


4.重量级锁

指的是原始的Synchronized的实现,重量级锁的特点:其他线程试图获取锁时,都会被阻塞,只有持有锁的线程释放锁之后才会唤醒这些线程。



偏向锁、轻量级锁、重量级锁优劣势比较

技术图片


没钱没人脉也能轻松入门,让你每年多赚10万!

技术图片






















以上是关于synchronized 底层如何实现?啥是锁的升级,降级的主要内容,如果未能解决你的问题,请参考以下文章

synchronize底层实现原理以及相关的优化

重新认识synchronized(下)

Java多线程系列:深入详解Synchronized同步锁的底层实现

Java多线程系列:深入详解Synchronized同步锁的底层实现

synchronized关键字的使用及互斥锁的实现

Java并发之synchronized关键字深度解析