synchronized 底层原理总结

本文总结下Synchronized关键字的底层实现原理。

一.synchronized介绍

synchronized是JVM内置锁,通过内部对象Monitor(监视器锁)来实现,基于进入与退出monitor对象来实现方法与代码块的同步,监视器锁的实现,最终依赖操作系统的Mutex lock(互斥锁)来实现。
在这里插入图片描述

二.synchronized使用方式

synchronized 主要有3种使用方式。

1.同步类方法

public synchronized void method()
{
   // todo
}

锁的是当前类对象;

2.同步代码块

public class TestNotes {
	private static Object object;
	
    public String decStock() {
        synchronized (object) {
            //todo
        }
        return "下单成功";
    }
}

或者

public  void run() {
   synchronized(this) {
      //todo
   }
}

锁的是括号里面的对象;

3.修饰一个类

lass ClassName {
   public void method() {
      synchronized(ClassName.class) {
         // todo
      }
   }
}

三.java对象组成

在这里插入图片描述
java对象由3部分组成:

  • 对象头 :比较复杂,synchronized加的锁,就保存在这里面的某些数据位。对象头大小是固定的;
  • 实例数据 :就是对象的业务数据;
  • 对齐填充位 :64位jvm,默认需要对象大小必须位8byte(字节)的整数倍,所以有时候需要对齐填充位。

Mark Word

其中,锁的不同状态,就是存在对象头中的Mark Word区域中,
在这里插入图片描述
下图是32位系统的Mark Word的区域具体分布:
在这里插入图片描述

四.锁的升级过程

synchronized锁有如下4种状态:

  • 无锁,不锁住资源,多个线程只有一个能修改资源成功,其他线程会重试;
  • 偏向锁,同一个线程获取同步资源时,没有别人竞争时,去掉所有同步操作,相当于没锁;
  • 轻量级锁,多个线程抢夺同步资源时,没有获得锁的线程使用CAS自旋等待锁的释放;
  • 重量级锁,多个线程抢夺同步资源时,使用操作系统的互斥量进行同步,没有获得锁的线程阻塞等待唤醒;

锁的升级过程:无锁-》偏向锁-》轻量级锁-》重量级锁。注意,升级并不一定是一级级升的,有可能跨级别,比如由无锁状态,直接升级为轻量级锁。

下面,我们以这段代码为例,分析以下锁的具体升级过程:

public class TestNotes {
	private static Object object;
	//减库存
    public String decStock() {
        synchronized (object) {
            //todo
        }
        return "下单成功";
    }
}

这段代码中,synchronized锁的是object实例对象,锁的具体状态,就保存在object对象头部的markword区域。

1. 无锁状态:

在这里插入图片描述
步骤说明:
1.synchronized锁的object对象头部markword区域,最开始锁状态标志位,默认值就是001,也就是无锁状态。
在这里插入图片描述

2. 偏向锁状态:

某刻,线程1执行到同步代码块,虚拟机会使用CAS尝试修改状态标志位,修改为偏向锁状态,并且把线程1的线程ID记录到markword区域的23bit位,进入偏向锁状态,如下图:
在这里插入图片描述
进入偏向锁状态后,如果没有其他线程竞争,线程1后续再次访问同步代码块时,犹如没有锁一样,jvm不会再进行CAS加锁、解锁等步骤,直接运行同步块代码。直到线程1执行完毕后,jvm会释放偏向锁,将markword的标识位恢复初始状态。

3. 轻量级锁状态:

线程1在持有偏向锁期间,线程2来了,下图右侧部分是线程2执行过程:
在这里插入图片描述
线程2访问同步代码块,尝试获取锁;此时jvm会检查线程1的状态,因为线程1还持有锁,jvm不能撤销线程1的锁,此时,jvm就会把锁升级位轻量级锁,也就是这个23bit区域存了线程1的地址,指向线程1的线程栈中的某块区域;同时线程栈的这块内存也保存了指向markword的引用,相当于两块区域互换了内容。

上文中描述的过程,是由无锁,然后变为偏向锁,然后是轻量级锁;
但是有些场景,锁会直接由无锁升级为轻量级锁,比如下图过程:
在这里插入图片描述
上图中,某一时刻,同时有两个线程执行到同步代码块,但实际肯定只能有一个线程先进入,假如是线程1,那么此时就会直接进轻量级锁状态。

此时,线程2就会进行CAS自旋,寻找机会获取轻量级锁,如下图:
在这里插入图片描述

4. 重量级锁状态:

上图中,进入轻量级锁状态后,线程2还会继续自旋尝试获取锁;这个时候,synchronized并不会立即进入重量级锁状态,而是等到线程2 自旋达到一定次数后,jvm才膨胀为下图的重量级锁。这个自旋次数,jdk7及以后可以通过jvm参数设置。
在这里插入图片描述
线程1执行完同步代码块,jvm尝试释放锁,修改markword为初始的无锁状态,在释放锁的时候,发现已经是重量锁了,说明有其他线程竞争,并且其他线程肯定已经进入了阻塞状态,那么jvm在释放锁之后,还会唤醒其他进入阻塞状态的线程。

五.总结

synchronized在jdk 1.6版本进行了优化,性能有了巨大提升,基本上和java锁性能没有什么差异,所以在生产环境中,synchronized能满足的场景,尽量使用synchronized,简单方便。

优化的关键,是加入了轻量级锁,使用CAS,还有自适应机制,避免了向底层操作系统申请互斥量,避免了用户态和内核态的切换,也就是在一定程度上避免了线程上下文的切换,暂时不进入重量级锁的状态。

发布了62 篇原创文章 · 获赞 29 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/csdn_20150804/article/details/104762106