1.基础知识

Synchronized 原理 - 图1
synchronized关键字最主要有以下3种应用方式,下面分别介绍
修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

2.Synchronized底层实现

2.1 同步代码块

  1. public static void main(String[] args) {
  2. int i = 1;
  3. new SychronizedDemo().add(i);
  4. System.out.println(i);
  5. }
  6. public void add(int i) {
  7. synchronized (this) {
  8. i++;
  9. }
  10. }

image.png

  1. 两个monitorexit 指令:
  2. 第一个monitorexit指令是同步代码块正常释放锁的一个标志;
  3. 如果同步代码块中出现Exception或者Error,则会调用第二个monitorexit指令来保证释放锁

2.1.1 monitorenter

  • 每个对象维护着一个记录着被锁次数的计数器,对象未被线程所占用时,该计数器为0,线程进入monitor(执行monitorenter指令后),会把计数器设置为1。
  • 若线程已拥有monitor的所有权,允许它重入monitor,进入一次次数+1 ;
  • 若其他线程已经占有monitor,当前尝试获取monitor的线程会被阻塞,直到进入次数为变0,才能重新被再次获取。

    2.1.2 monitorexit

  • 能执行monitorexit指令的线程,一定是拥有当前对象的monitor所有权的。

  • 当执行monitorexit指令计数器减到为0时,当前线程就不再拥有monitor所有权。其他被阻塞的线程即可再一次去尝试获取这个monitor的所有权。

    2.2 同步方法

    1. public class SychronizedDemo {
    2. public static void main(String[] args) {
    3. int i = 1;
    4. new SychronizedDemo().add(i);
    5. System.out.println(i);
    6. }
    7. public synchronized void add(int i) {
    8. System.out.println(i++);
    9. }
    10. }

    image.png

  • synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。

3.Synchronized锁升级过程

image.png
image.png
image.png

偏向锁 轻量级锁 重量级锁
适用场景 只有一个线程进入同步块 虽然多个线程,但是没有冲突;多条线程进入同步块,但是线程进入时间错开因而并未争抢锁 发生了锁争夺的情况:多条线程争夺锁。
本质 没有同步操作 CAS操作代替互斥同步 互斥同步
优点 不阻塞,执行效率高 自旋不阻塞 不会空耗CPU,直接挂起阻塞。
缺点 适用场景太局限。若竞争产生,会有额外的偏向锁撤销的消耗。 长时间获取不到锁空耗CPU 阻塞,上下文切换。

锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。

3.1 偏向锁

偏向锁是Java 6之后加入的新锁,它是一种针对加锁操作的优化手段,经过研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁(会涉及到一些CAS操作,耗时)的代价而引入偏向锁。偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。

所以,对于没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续多次是同一个线程申请相同的锁。但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。下面我们接着了解轻量级锁。

3.1.1 为什么要引入偏向锁?

大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。减少CPU切换,用户态到核心态的切换

3.1.2 偏向锁的升级

【1】当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁;
因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;
【2】如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活, 如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;
【3】如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,
【4】如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。

3.2 轻量级锁

倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的),此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。需要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。

3.2.1 为什么引入轻量级锁?

轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。

3.2.2 自旋锁

轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。这是基于在大多数情况下,线程持有锁的时间都不会太长,如果直接挂起操作系统层面的线程可能会得不偿失。

3.2.2.1 自适应自旋锁

JDK1.6引入了自适应自旋锁。线程如果上次自旋成功了,那么这次自旋的次数会更加多。因此虚拟机认为既然上次成功了,那么这次

3.2.3 轻量级锁什么时候升级为重量级?

线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址;
如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。

但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

3.3 重量级锁


内置锁在Java中被抽象为监视器锁(monitor)。在JDK 1.6之前,监视器锁可以认为直接对应底层操作系统中的互斥量(mutex)。这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。因此,后来称这种锁为“重量级锁”;

3.4 锁消除

消除锁是虚拟机另外一种锁的优化,这种优化更彻底,Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间。

引用

https://segmentfault.com/a/1190000017255226
http://www.360doc.com/content/20/0602/17/70282097_916085028.shtml
https://blog.csdn.net/javazejian/article/details/72828483
https://blog.csdn.net/suifeng629/article/details/106156599
Synchronized 汇编底层实现:https://zhuanlan.zhihu.com/p/118634086