• synchronized的底层实现


    引言

    在单机环境中的并发编程中,需要用锁来保证数据的安全性。我们经常会用到synchronized,那么JVM中是如何实现synchronized的呢,在这篇文章中,我会从锁分类和锁膨胀(锁升级)的角度,会来探析一二。

    为什么要有锁对象

    Object lockObj = new Object();
    synchronized(lockObj){
        //TODO
    }
    

    在访问某块代码或者变量时,为了防止线程安全问题,我们需要先获取锁对象,通过锁的互斥来保证线程的安全访问。在上面这块代码中,lockObj就是锁对象。

    synchronized底层实现的锁分类

    1. 偏向锁
    2. 轻量级锁
    3. 重量级锁
      在这3种分类中,偏向锁和轻量级锁是在逻辑层面做的一些处理,并非真正的锁,只有重量级锁,才是真正的锁(操作系统层面的锁)。

    为什么要对锁分类

    假设现在有A和B两个线程,要访问共有变量。一般来说有如下3种情况:

    1. 每次只有线程A或者线程B单独使用。
    2. 线程A和线程B交替使用。
    3. 线程A和线程B,同时使用。

    众所周知,获取锁是比较消耗性能的。所以在Java中,synchronized提供了几种锁的实现来优化。
    对于这3种情况,前两种可以在逻辑层面做一些处理,避免每次获取锁(操作系统锁),带来的性能开销。对于1,可以使用偏向锁。对于2,可以使用轻量级锁。

    偏向锁

    偏向锁会保证对象被线程安全的访问。

    锁对象

    被synchronized锁保护的,称作锁对象。锁对象中包含了锁对象头,由线程idEpoch、分代年龄、是否偏向锁标记、锁标记组成。

    线程id:每次获取锁对象时,会先检查线程id是否与当前线程一致,如果线程id是空,则通过CAS设置对象头中的线程id。
    Epoch:本质是时间戳。使用Epoch通过CAS来保证设置线程id的安全性。

    运行原理

    在获取锁对象时,首先会检查锁对象头中的线程id是否与当前线程一致。

    1. 如果线程id是空,则通过CAS设置对象头中的线程id,并更新Epoch。
    2. 如果线程id与当前线程一致,则可以安全访问。
    3. 如果线程id与当前线程不一致,则需要锁膨胀。(升级为轻量级锁)

    轻量级锁

    在偏向锁获取不到锁对象时,会通过自旋来不断的尝试获取锁,这就称为轻量级锁。

    重量级锁

    在通过一定的自旋次数后,如果还获取不到锁,就会升级为重量级锁,所有获取不到锁对象的线程都会被阻塞(Blocked状态)。
    重量级锁会使用Monitor获取操作系统的MutexLock(互斥锁)

    什么时候切换锁类型

    在默认情况下,会先尝试使用偏向锁,如果获取不到,则升级为轻量级锁,轻量级锁在一定的自旋次数后,会升级为重量级锁。获取锁是按:偏向锁->轻量级锁->重量级锁,依次升级,且无法降级。
    只有当当前锁无法获取到锁对象时,才会升级。

  • 相关阅读:
    tcp传送报文
    整理下本周工作中遇到的疑问;uid/euid/suid;docker镜像管理
    网络隔离
    ubuntu 只有客人会话登录(第一次深刻感受文件权限的威力 )
    ubuntu 只有客人会话登录(第一次深刻感受文件权限的威力)
    使用gdb查看栈帧的情况,有ebp
    使用gdb查看栈帧的情况, 没有ebp
    再看perf是如何通过dwarf处理栈帧的
    dwarf是如何处理栈帧的?
    数据库设计的误区—>CHAR与VARCHAR
  • 原文地址:https://www.cnblogs.com/wugang/p/14285239.html
Copyright © 2020-2023  润新知