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(互斥锁)

    什么时候切换锁类型

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

  • 相关阅读:
    单例模式
    Curator Zookeeper分布式锁
    LruCache算法原理及实现
    lombok 简化java代码注解
    Oracle客户端工具出现“Cannot access NLS data files or invalid environment specified”错误的解决办法
    解决mysql Table ‘xxx’ is marked as crashed and should be repaired的问题。
    Redis 3.0 Cluster集群配置
    分布式锁的三种实现方式
    maven发布项目到私服-snapshot快照库和release发布库的区别和作用及maven常用命令
    How to Use Convolutional Neural Networks for Time Series Classification
  • 原文地址:https://www.cnblogs.com/wugang/p/14285239.html
Copyright © 2011-2022 走看看