zoukankan      html  css  js  c++  java
  • 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(互斥锁)

    什么时候切换锁类型

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

  • 相关阅读:
    bootstrap 网格系统学习
    在asp.net web api中利用过滤器设置输出缓存
    解决在开发环境中访问json配置文件时报HTTP 错误 404.3
    Newtonsoft.Json序列化和反序列
    装饰者模式学习
    SQL server跨库查询
    python-安装xlrd xlwt 插件
    vim 实际行跟屏幕行移动命令
    vim-缓存区中打开另外一个文件的方法
    vim 计算器寄存器使用
  • 原文地址:https://www.cnblogs.com/wugang/p/14285239.html
Copyright © 2011-2022 走看看