Synchronized 底层原理总结

328 阅读7分钟

一.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,还有自适应机制,避免了向底层操作系统申请互斥量,避免了用户态和内核态的切换,也就是在一定程度上避免了线程上下文的切换,暂时不进入重量级锁的状态。

系列面试题

另外一直在做Java面试连载的系列专栏,这回大家终于可以白嫖了

2021年中高级最全Java面试题,肝了!

阿里2021最全新的java面试题总结

字节跳动今日头条前端面经(4轮技术面+hr面)

吃透Java IO:字节流、字符流、缓冲流—最详细总结!

每日一面:1. JDK 、 JRE 、JVM有什么区别和联系?

Java高级进阶,你必须要掌握这些数据结构

Java并发面试,幸亏有点道行,不然又被忽悠了

Java面向对象设计?一招教你学会OOPS编程

java动态代理机制--那些让你面试脱颖而出的技能

你真正了解Java虚拟机吗—高级开发必备《深入了解Java虚拟机》

彻底理解Java中的值传递和引用传递

Java上亿级别秒杀系统优化 ,实现真正的高性能高并发!

深入了解JAVA的线程中断方法经验之总结

深入了解Java之类加载和案例分析

深入了解Java之垃圾回收

java设计模式六大原则之场景应用分析

在Java中如何优雅地判空

这样写简历,offer不给你给谁?

作者:雨剑yyy 链接:blog.csdn.net/csdn_201508… 来源:csdn

synchronized关键字是并发编程不可或缺的部分,Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

Synchronized是Java中解决并发问题的一种最常用最简单的方法 ,他可以确保线程互斥的访问同步代码

个人认为能真实理解其内部运作原理能对平时的开发带来很大意义上的帮助,希望这篇文章能帮助你!

本文由博客一文多发平台 OpenWrite 发布!