栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

02 多线程与高并发 - synchronized 解析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

02 多线程与高并发 - synchronized 解析

文章目录
    • CAS
    • 对象在内存中的布局
    • MarkWord 信息
    • synchronized 锁升级
    • ObjectMonitor
    • 其他概念
    • 相关问题

CAS

compare and swap,比较和交换

线程基于 CAS 修改数据的方式:先获取主内存数据,在修改之前,先比较数据是否一致,如果一致修改主内存数据,如果不一致,放弃这次修改

存在的问题

  • CAS 只能对一个变量的修改实现原子性,一段代码中多个变量则无法保证未经 CAS 修饰的变量的原子性
  • 存在 ABA 问题(解决方案:加版本号)
  • CAS 执行次数过多,但是依旧无法实现对数据的修改,CPU 会一直调度这个线程,造成对 CPU 的性能损耗(轻量锁不一定比重量锁好的原因)
对象在内存中的布局

  • MarkWord 对象标记:
    包含一系列的标记位,比如轻量级锁的标记位、偏向锁标记位、gc记录信息等等。在32位系统占4字节,在64位系统中占8字节。
  • Class Point:
    用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。在32位系统占4字节,在64位系统中占8字节。
  • 实例数据(对象里面的属性)
  • 补齐填充(会补齐为 8 的整数倍,缓存行对齐)

涨姿势啦!Java程序员装X必备词汇之Mark Word!

在 java 中查看对象布局


    org.openjdk.jol
    jol-core
    0.9


Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());

MarkWord 信息


上面图片中红色框记录了MarkWord 的信息,看标志位 01 对照上面的表可以看到当前对象处于无锁态

synchronized 锁升级

  • 偏向锁是否启动:偏向锁是延迟开启的,并且在开启偏向锁之后,默认不存在无锁状态

    • synchronized在偏向锁升级到轻量锁时,会涉及到偏向锁撤销,需要等到一个安全点,stw,才可以撤销,并发偏向锁撤销比较消耗资源
    • 在程序启动时,偏向锁有一个延迟开启的操作,因为项目启动时,ClassLoader会加载.class文件,这里会涉及到synchronized操作,
    • 为了避免启动时,涉及到偏向锁撤销,导致启动效率变慢,所以程序启动时,默认不是开启偏向锁的。
// 关闭延迟开启偏向锁
-XX:BiasedLockingStartupDelay=0
  • 无锁态、匿名偏向:没有线程拿锁。

  • 偏向锁:没有线程的竞争,只有一个线程再获取锁资源。
    线程竞争锁资源时,发现当前synchronized没有线程占用锁资源,并且锁是偏向锁,使用CAS的方式,设置o的线程ID为当前线程,获取到锁资源,下次当前线程再次获取时,只需要判断是偏向锁,并且线程ID是当前线程ID即可,直接获得到锁资源。

  • 轻量级锁:偏向锁出现竞争时,会升级到轻量级锁(触发偏向锁撤销)。一个线程竞争到了(记录Lock Record)其他线程开始自旋
    轻量级锁的状态下,线程会基于CAS的方式,尝试获取锁资源,CAS的次数是基于自适应自旋锁实现的,JVM会自动的基于上一次获取锁是否成功,来决定这次获取锁资源要CAS多少次。

  • 重量级锁:轻量级锁CAS一段次数后,没有拿到锁资源,升级为重量级锁(其实CAS操作是在重量级锁时执行的)。
    重量级锁就是线程拿不到锁,进入等待队列,不需要消耗 CPU 资源。

ObjectMonitor

涉及 ObjectMonitor 一般是到达了重量级锁才会涉及到。在到达重量级锁之后,重量级锁的指针会指向ObjectMonitor 对象。

 ObjectMonitor() {
    _header       = NULL;
    _count        = 0;     // 抢占锁资源的线程个数
    _waiters      = 0,     // 调用wait的线程个数。
    _recursions   = 0;     // 可重入锁标记,
    _object       = NULL; 
    _owner        = NULL;  // 持有锁的线程
    _WaitSet      = NULL;  // wait的线程  (双向链表)
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;  // 假定的继承人(锁释放后,被唤醒的线程,有可能拿到锁资源)
    _cxq          = NULL ;  // 挂起线程存放的位置。(单向链表)
    FreeNext      = NULL ;
    _EntryList    = NULL ;  // _cxq会在一定的机制下,将_cxq里的等待线程扔到当前_EntryList里。  (双向链表)
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

地址:
http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/runtime/objectMonitor.hpp

其他概念

编译器优化:
锁消除:线程在执行一段 synchronized 代码块时,发现没有共享数据的操作,自动帮你把 synchronized 去掉。
锁膨胀:在一个多次循环的操作中频繁的获取和释放锁资源,synchronized 在编译时,可能会优化到循环外部。

相关问题
  1. 单例模式下DCL是否需要加 volatile (半初始化,指令重排)
    需要
class T{
	int n = 1;
}
T t = new T();

正常创建过程:
(1)申请一块内存空间,new 对象,此时 n 为初始值 0
(2)调用构造方法,n = 1
(3)t 与 新建的对象建立关联

申请内存,初始化,关联是正常顺序,如果CPU对指令重排,可能会造成
申请内存,关联,初始化,在还没有初始化时,其他线程来获取数据,导致获取到的数据虽然有地址引用,但是内部的数据还没初始化,都是默认值,导致使用时,可能出现与预期不符的结果
//DCL 双重校验加锁
public class Single {
 
    private volatile static Single instance ;   //volatile 修饰,防止指令重排
 
    private Single(){}
 
    public static Single getInstance(){
 
        if(instance==null){
            synchronized (Single.class){    
                if(instance==null){
                    instance = new Single();  
                }
            }
           
        }
        return instance;
    }

}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1031840.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号