csworkman

个人博客

JIT编译器对锁的优化

2022-6-16 Mr Chang JUC

JIT————Just In Time Compiler 一般翻译为即时编译器。

评论(0) 浏览(1393)

Synchronized与锁升级

2022-6-16 Mr Chang JUC

  1. Synchronized 锁优化的背景
    用锁能够实现数据的安全性,但是会带来性能下降。无锁能够基于线程并行提升程序性能,但是会带来安全性下降。
    求平衡???
  2. 升级流程
    Synchronized用的锁是存在Java对象头里的Mark Word中
    锁升级功能主要依赖于MarkWord中锁标志位和释放偏向锁标志位
  3. 锁指向,请牢记
    偏向锁:MarkWord存储的是偏向的线程ID;
    轻量锁:MarkWord存储的是指向线程栈中Lock Record的指针;
    重量锁:MarkWord存储的是指向堆中的monitor对象的指针;

无锁:
初始状态,一个对象被实例化后,如果没有被任何线程竞争锁,那么它就为无锁状态(001)

偏向锁:
单线程竞争
注意:偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的。
在没有其他线程竞争的时候,一直偏向偏心当前线程,当前线程可以一直执行。偏向于第一个获得它的线程。执行完同步代码块后,线程并不会主动释放偏向锁。

题外话  Java15 逐步废弃偏向锁 默认在虚拟机当中不再开启
重点课程对应 谷粒学院JUC 课时130


轻量级锁:
  1. 主要作用:有线程来参与锁的竞争,但是获取锁的冲突时间极短,本质就是自旋锁CAS
  2. 获取:如果关闭偏向锁,则自动升级为轻量级锁
  3. 如果轻量级锁在自旋达到一定次数仍然不能成功,则升级为重量级锁。
  4. 自适应自旋的大致原理
    线程如果自旋成功了,那下次自旋的最大次数增加,因为JVM认为既然上次成功了,那么这一次也很大概率成功。
    反之,如果很少会自旋成功,那么下次会减少自旋的次数甚至不自旋,避免CPU空转。
轻量锁和偏向锁的区别和不同:
  1. 争夺轻量级锁失败时,自旋尝试抢占锁
  2. 轻量级锁每次退出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁
重量级锁 ————指向互斥量(重量级锁)的指针

重量级锁原理
Java中synchronized的重量级锁,是基于进入和退出Monitor对象实现的。在编译时会将同步块的开始位置插入monitor enter 命令,在结束位置插入monitor exit 指令

当线程执行到monitor enter指令时,会尝试获取对象所对应的Monitor所有权,如果获取到了,即获取到了锁,会在Monitor的owner中存放当前线程的id, 这样它将处于锁定状态,除非退出同步块,否则其他线程无法获取到这个Monitor。

JDK1.6之后进行了优化,拥有了无锁->偏向锁->轻量级锁->重量级锁的升级过程,而不是无论什么情况都使用重量级锁。

评论(0) 浏览(1358)

对象内存布局

2022-6-16 Mr Chang JUC

整体结构包括三部分:
  1. 对象头部 ———— 只有一个对象头的实例对象 只有16字节
    对象标记  _mark 字段是mark word 
    类型指针 _metadata  字段是 klass pointer 
    对象头(object header)即是由这两个字段组成
    这些术语可以参考Hotspot术语表

  2. 实例数据
  3. 对齐填充


尾巴参数说明————压缩指针相关说明命令
  1. java -XX: +PrintCommandLineFlags -version
  2. 默认开启压缩指针,-XX: +UseCompressedClassPointers
     12 + 4 (对齐填充) = 一个对象16字节
  3. 手动配置,关闭了压缩指针 -XX: -UseCompressedClassPointers,
    8+8 = 一个对象16字节

评论(0) 浏览(1179)

ThreadLocal——总结

2022-6-13 Mr Chang JUC

最佳实践:
  1. ThreadLocal.withInitial(() -> 初始化值);
  2. 建议把ThreadLocal 修饰为static。
    说明:这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量,也就是在类第一次呗使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量。
  3. 用完记得手动remove


总结:
  • ThreadLocal 并不解决线程间共享数据的问题
  • ThreadLocal适用于变量在线程间隔离并且在方法间共享的场景
  • ThreadLocal通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题,每个线程持有一个只属于自己的专属Map并维护了ThreadLocal对象与具体实例的映射,该Map由于只被持有它的线程访问,故不存在线程安全以及锁的问题。
  • ThreadLocalMap的Entry对ThreadLocal的引用为弱引用,避免了ThreadLocal对象无法被回收的问题都会通过expungeStateEntry,cleanSomeSlots,replaceStaleEntry这三个方法回收键为null的Entry对象的值以及Entry对象本身从而防止内存泄漏。属于安全加固的方法,群雄逐鹿起纷争,人各一份天下安。




评论(0) 浏览(1134)

ThreadLocalMap 与 thread 和 threadLocal 的关系

2022-6-10 Mr Chang JUC

threadLocalMap实际上就是一个以threadLocal实例为key,任意对象为value的Entry对象,
        void  createMap(ThREAD t, T firstValue) {
              t.threadLocals = new ThreadLocalMap(this, firstValue); 
        }

图片形象化解释:
        1654828378(1).jpg

评论(0) 浏览(1108)

LongAdder———源码分析

2022-6-9 Mr Chang JUC

AtomicLong 利用 CAS原理不停的在自旋 导致性能消耗,如果涉及高并发,大数据,,,量变引起质变!!! 

LongAdder 利用cell数组将热点进行拆分,最后得到统计求和的结果。
result = base + Cell 数组  上述两者全部,才是最后OK。
  1. 最初无竞争时只更新base;
  2. 如果更新base失败后,首次新建一个cell[]数组
  3. 当多个线程竞争同一个Cell比较激烈时,可能就要对Cell[]扩容
       Add方法源码解析
 
        1654760883(1).jpg
  1. 如果Cells表为空,尝试用CAS更新base字段,成功则推出。
  2. 如果Cells表为空,CAS更新base字段失败,出现竞争,uncontended为true,调用longAccumulate;
  3. 如果Cells表非空,当前线程映射的槽为空,uncontended为true,调用longAccumulate;
  4. 如果Cells表非空,且前线程映射的槽非空,CAS更新Cell的值,成功则返回,否则uncontended设为false,调用longAccumulate。
        longAccumulate方法源码解析
       

评论(0) 浏览(1100)

CAS

2022-6-9 Mr Chang JUC

CAS 是靠硬件实现的从而在硬件层面提升效率,最底层还是交给CPU来保证原子性和可见性。实现方式是基于硬件平台的汇编指令,在intel的CPU中(X86机器),使用的是汇编指令cmpxchg指令。

核心思想就是:比较要更新变量的值V和预期值E(compare),相等才会将V的值设为新值N(swap)如果不相等自旋再来。


CAS两大缺点:
  1. 循环导致时间过长开销大
  2. ABA问题
    1654742119(1).jpg
    解决办法: 比较+版本号一致










































































评论(0) 浏览(947)

volatile——介绍

2022-6-7 Mr Chang JUC

  1. 特点:可见性,有序性
  2. 语义:
    当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量立即刷新回主内存中。
    当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,重新回到主内存中读取最新共享变量。

       内存屏障
1654583582(1).jpg
  1. 由于volatile变量只能保证可见性,在不符合以下两条规则的运算场景中,我们仍然要通过加锁来保证原子性
    运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
    变量不需要与其他的状态变量共同参与不变约束。

  2. 禁止重新排序的三种情况(重排序发生,会导致程序运行结果不同。)
    写后读————a=1; b=a; ————写一个变量之后在读这个变量
    写后写————a=1; a=2;———— 写一个变量之后,在写这个变量
    读后写————a=b;b=1;——————读一个变量之后,在写这个变量

评论(0) 浏览(941)

happens-before之8条

2022-6-7 Mr Chang JUC

  1. 次序规则
     一个线程内,  按照代码顺序,写在前面的操作线性发生于写在后面的操作;
  2. 锁定规则
    一个unLock操作先行发生于后面(这里的“后面”是指时间上的先后)对同一个锁的lock操作
  3. volatile变量规则
    对一个volatile变量的写操作先行发生于后面对这个变量的读操作。前面的写对后面的读是可见的。
  4. 传递规则
    如果操作A先行发生于操作B,操作B先行发生于操作C,则可以得出操作A先行发生于操作C
  5. 线程启动规则(Thread Start Rule)
    线程对象的start方法先行发生与线程里面的每一个操作。
  6. 线程中断规则(Thread interruption Rule
    先调用interrupt()方法设置过中断标志位,我才能检测到中断发送
  7. 线程终止规则(Thread Termination Rule
    线程中的所有操作都先行发生于对此线程的终止检测。可以通过isAlive()检测线程是否已经终止
  8. 对象终结规则(Finalizer Rule)
    一个对象的初始化构造函数完成 先行发生于它的finalize()方法的开始

评论(0) 浏览(891)

happens-before总原则

2022-6-7 Mr Chang JUC

  1. 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见。而且第一个操作的执行顺序排在第二个操作之前。
  2. 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。

评论(0) 浏览(878)