CPU|java面试——常见的锁和锁升级( 六 )


  • 竞争进一步加剧 , 出现了大量的线程在自旋 , 或者自旋超过一定的次数 , 这时候cpu空转比较厉害 , 为了节省cpu资源 , jvm开始向操作系统申请重量级锁——synchronized、ReentrantLock ,
  • 有个上面的基础 , 可以解释synchronized的执行过程?
    1. 检测Mark Word里面是不是当前线程的ID , 如果是 , 表示当前线程处于偏向锁
    2. 如果不是 , 则使用CAS将当前线程的ID替换Mard Word , 如果成功则表示当前线程获得偏向锁 , 置偏向标志位1
    3. 如果失败 , 则说明有锁竞争 , 撤销偏向锁 , 进而升级为轻量级锁 。
    4. 当前线程使用CAS将对象头的Mark Word替换为当前线程的lock record , 如果成功 , 当前线程获得锁, 如果失败 , 则自旋不停的获取锁 。
    5. 当自旋一定次数之后获取成功了 , 还是用轻量级锁 , 如果失败了 , 锁再次升级为重量级锁 , 之前自旋的线程进入wait状态 , 等待cpu分配时间片后再次执行
      如果线程争用激烈 , 那么应该禁用偏向锁 。
    锁消除
    总结:对于不可能共享的资源 , 比如局部变量 , 在执行的时候jvm会把对象锁消除 , 比如一个方法里面的stringBuffer的append()
    锁粗话
    总结:假如有一个循环 , 循环内的操作需要加锁 , 我们应该把锁放到循环外面 , 否则每次进出循环 , 都进出一次临界区 , 效率是非常差的
    消除缓存行的伪共享除了我们在代码中使用的同步锁和jvm自己内置的同步锁外 , 还有一种隐藏的锁就是缓存行 , 它也被称为性能杀手 。在多核cup的处理器中 , 每个cup都有自己独占的一级缓存、二级缓存 , 甚至还有一个共享的三级缓存 , 为了提高性能 , cpu读写数据是以缓存行为最小单元读写的;32位的cpu缓存行为32字节 , 64位cup的缓存行为64字节 , 这就导致了一些问题 。例如 , 多个不需要同步的变量因为存储在连续的32字节或64字节里面 , 当需要其中的一个变量时 , 就将它们作为一个缓存行一起加载到某个cup-1私有的缓存中(虽然只需要一个变量 , 但是cpu读取会以缓存行为最小单位 , 将其相邻的变量一起读入) , 被读入cpu缓存的变量相当于是对主内存变量的一个拷贝 , 也相当于变相的将在同一个缓存行中的几个变量加了一把锁 , 这个缓存行中任何一个变量发生了变化 , 当cup-2需要读取这个缓存行时 , 就需要先将cup-1中被改变了的整个缓存行更新回主存(即使其它变量没有更改) , 然后cup-2才能够读取 , 而cup-2可能需要更改这个缓存行的变量与cpu-1已经更改的缓存行中的变量是不一样的 , 所以这相当于给几个毫不相关的变量加了一把同步锁; 为了防止伪共享 , 不同jdk版本实现方式是不一样的: \\1. 在jdk1.7之前会 将需要独占缓存行的变量前后添加一组long类型的变量 , 依靠这些无意义的数组的填充做到一个变量自己独占一个缓存行; \\2. 在jdk1.7因为jvm会将这些没有用到的变量优化掉 , 所以采用继承一个声明了好多long变量的类的方式来实现; \\3. 在jdk1.8中通过添加sun.misc.Contended注解来解决这个问题 , 若要使该注解有效必须在jvm中添加以下参数: -XX:-RestrictContended
    sun.misc.Contended注解会在变量前面添加128字节的padding将当前变量与其他变量进行隔离;
    【CPU|java面试——常见的锁和锁升级】参考资料:https://www.cnblogs.com/linghu-java/p/8944784.html