网页设计代码为什么没有颜色,镇江整站优化,华天动力oa系统,内容营销公司文章目录 1、轻量锁2、轻量锁的作用3、轻量锁的加锁和释放4、轻量级锁的代码演示5、重量级锁6、重量级锁的原理7、锁升级和hashcode的关系8、锁升级和hashcode关系的代码证明9、synchronized锁升级的总结10、JIT编译器对锁的优化#xff1a;锁消除和锁粗化11、结语 #x1f4… 文章目录 1、轻量锁2、轻量锁的作用3、轻量锁的加锁和释放4、轻量级锁的代码演示5、重量级锁6、重量级锁的原理7、锁升级和hashcode的关系8、锁升级和hashcode关系的代码证明9、synchronized锁升级的总结10、JIT编译器对锁的优化锁消除和锁粗化11、结语 相关笔记 【synchronized锁升级之 无锁】 【synchronized锁升级之 偏向锁】
1、轻量锁
前面一篇提到偏向锁即只有一个线程在竞争此时通过资源类对象的对象头的Mark Word来标记避免了用户态和内核态的频繁切换。 再往下又来了一个线程也来竞争这个锁且此时这两个线程近乎可以错开交替执行或者说同步代码块/方法执行一次时间很短哪怕另一个线程等也不会等太久如下图的1、2、3、4标号 这就是轻量级锁的出现场景有线程来参与竞争了但不存在锁竞争太过激烈的情况获取锁的冲突时间极端本质就是CAS自旋锁不要直接往重锁走。对应的共享对象内存图 2、轻量锁的作用 轻量锁是为了在两个线程近乎交替执行同步块时来提高性能。 直白说就是先CAS自旋不行了再考虑升级为重锁使用操作系统的互斥量。升级到轻量锁的时机有
关闭了偏向锁多线程竞争偏向锁可能导致偏向锁升级为轻量锁这里写可能是因为如果恰好是一个线程over一个线程上位则依旧是偏向锁
举个例子比如现有A线程拿到了锁A一个人走偏向锁玩了一会儿后线程B来了B在争抢时发现共享对象的对象头中Mark Word里的线程ID标记不是线程B的ID而是线程A此时B线程通过CAS来尝试修改标记。当
此时线程A刚好OverB上位修改Mark Word里的线程ID为B此时仍为偏向锁且偏向B 如果A正在执行B修改失败则升级为轻量级锁且轻量级锁继续由原来的线程A持有接着执行刚才没执行完的而线程B则自旋等待获取这个轻量级锁 3、轻量锁的加锁和释放 加锁 JVM会在线程的栈帧中创建用于存储锁记录Lock Record的空间称为Displaced Mark Word。 若一个线程获得锁时发现是轻量级锁会把对象锁的MarkWord复制到自己的Displaced Mak Word里面。然后线程尝试用CAS将锁的MarkWord替换为指向锁记录的指针。如下面两幅草图示意的变化过程 如果替换成功当前线程获得轻量锁。如果失败表示Mark Word已经被替换成了其他线程的锁记录说明在与其它线程竞争锁当前线程就尝试使用自旋来获取锁自旋一定次数后仍未获得锁升级为重量锁。 轻量级锁的释放 在释放锁时当前线程会使里CAS操作将Displaced Mark Word的内容复制回对象锁的Mark Word里面。如果没有发生竞争。那么这个复制的操作会成功。如果持有锁期间有其他线程因为自旋多次导致轻量级锁升级成了重量级锁那么CAS操作会失败此时会释放锁并唤醒被阻塞的线程。
4、轻量级锁的代码演示
-XX:-UseBiasedLocking添加JVM参数关闭偏向锁就可以直接进入轻量级锁
Object object new Object();
new Thread(() - {synchronized (object){System.out.println(ClassLayout.parseInstance(object).toPrintable());}
}).start();
运行 轻量锁下自旋达到一定次数或者说程度会升级为重量锁
Java6之前默认情况下自旋的次数是10次或者自旋的线程数超过了cpu核数的一半可-XX:PreBlockSpin10来修改Java6之后JVM做了优化采用自适应自旋 自适应自旋即线程如果自旋成功了那下次自旋的最大次数会增加因为JVM认为既然上次成功了那么这一次也很大概率会成功。反之如果很少会自旋成功那么下次会减少自旋的次数甚至不自旋以避免CPU空转。直白说就是会总结前人的经验了、会预判走位了。 轻量锁与偏向锁的区别
偏向锁是一个线程自己在玩而偏向锁涉及竞争且争夺轻量级锁失败时自旋尝试抢占锁轻量级锁每次退出同步块都需要释放锁要不就不会是一个走了一个接上了而偏向锁则只在有线程来竞争时才释放锁
5、重量级锁
竞争太激烈时只能捅到重量级锁进行内核态和用户态的切换但前面偏向锁和轻量级锁已然做了一定程度的缓冲和优化了。 有大量的线程参与锁的竞争冲突性很高
Object object new Object();
//多个线程
for (int i 0; i 6; i) {new Thread(() - {synchronized (object){System.out.println(ClassLayout.parseInstance(object).toPrintable());}},String.valueOf(i)).start();
}运行 6、重量级锁的原理
Java中synchronized的重量级锁是基于进入和退出Monitor对象实现的。在编译时会将同步块的开始位置插入monitor enter指令在结束位置插入monitor exit指令。
当线程执行到monitor enter指令时会尝试获取对象所对应的Monitor所有权如果获取到了即获取到了锁会在Monitor的owner中存放当前线程的id这样它将处于锁定状态除非退出同步块否则其他线程无法获取到这个Monitor。
7、锁升级和hashcode的关系 可以看到无锁状态下Java对象头的Mark Word中是有空间存hashcode的锁升级后则没有位置了那要是锁升级后hashcode去哪儿了 总结下 1 在无锁状态下Mark Word可以存储对象的identity hash code值当对象的hashCode()方法第一次被调用时JVM会生成对应的identity hash code值存于对象头的Mark Word中。
2 对于偏向锁在线程获取偏向锁时用Thread Id和epoch值看成时间戳去覆盖identity hash code所在的位置。如果一个对象的hashcode()方法已经被调用过一次则这个对象不能被设置偏向锁因为如果可以那identity hash code就会被线程ID覆盖就会造成同一对象前后两次调用hashcode方法得到的结果不一致。
3 升级为轻量锁时JVM会在当前线程的栈帧中创建一个锁记录空间Lock Record前面已提到用于拷贝和存储锁对象的Mark Word里面自然包含了identity hash code、GC年龄等且释放轻量锁时这些数据又会写回对象头因此轻量级锁可以和identity hash code共存。
4 到重量级锁时Mark Word保存的是重量级锁指针而代表重量级锁的ObiectMonitor类里有字段记录了非加锁状态下的Mark Word锁释放以后也会写回对象头。
8、锁升级和hashcode关系的代码证明
Case1当一个对象已经计算过identity hashcode它就无法进入偏向锁状态会跳过偏向锁直接升级轻量级锁
//先睡5秒抵消偏向锁开启的延时保证开启偏向锁
TimeUnit.SECONDS.sleep(5);
Object object new Object();
System.out.println(这里应该是偏向锁);
System.out.println(ClassLayout.parseInstance(object).toPrintable());
//没有重写hashcode重写后无效
int hashCode object.hashCode();
//验证当一个对象已经计算过identity hash code后就无法进入偏向状态
new Thread(() - {synchronized (object){System.out.println(这里本应是偏向锁但刚才计算过一致性哈希hashcode这里会直接升级为轻量级锁 );System.out.println(ClassLayout.parseInstance(object).toPrintable());}
}).start(); Case2偏向锁过程中遇到一致性哈希计算请求立马撤销偏向模式膨胀为重量级锁
//先睡5秒抵消偏向锁开启的延时保证开启偏向锁
TimeUnit.SECONDS.sleep(5);
Object object new Object();
synchronized (object){System.out.println(ClassLayout.parseInstance(object).toPrintable());System.out.println(此时是偏向锁但下面一计算哈希会立马撤销偏向模式膨胀为重量级锁);//计算哈希值这里的hashcode方法是没有重写过的int hashCode object.hashCode();System.out.println(ClassLayout.parseInstance(object).toPrintable());
}9、synchronized锁升级的总结 synchronized锁升级目的还是实现一个性能优化思想就是先自旋不行了再阻塞。一直都是围绕尽量避免内核态和用户态频繁切换来展开的。实际上是把之前的悲观锁(重量级锁)变成在一定条件下使用偏向锁以及使用轻量级(自旋锁CAS)的形式。太精辟了这句道出了这几种锁的关系。 另外synchronized在修饰方法和代码块时在字节码上实现方式有很大差异但是内部实现还是基于对象头的MarkWord来实现的。JDK1.6之前synchronized使用的是重量级锁JDK1.6之后进行了优化拥有了无锁-偏向锁-轻量级锁-重量级锁的升级过程而不是无论什么情况都使用重量级锁。 最后
偏向锁适用于单线程的情况在不存在锁竞争的时候进入同步方法/代码块则使用偏向锁。轻量级锁适用于竞争较不激烈的情况(这和乐观锁的使用范围类似)轻量级锁采用的是自旋锁如果同步方法/代码块执行时间很短的话就很容易一个线程完事儿了另一个线程尚未哪怕不是这么刚刚好也自旋等不了太久采用轻量级锁自旋虽然会占用Cpu资源但是相对比使用重量级锁还是更高效。重量级锁适用于竞争激烈的情况如果同步方法/代码块执行时间很长那么使用轻量级锁自旋带来的性能消耗就比使用重量级锁一更严重这时候就需要升级为重量级锁。
10、JIT编译器对锁的优化锁消除和锁粗化
JIT即Just Time Compiler翻译即时编译器。 synchronized锁消除 以下是一个简单的synchronized代码没啥毛病别说优化成线程池
public class LockClearDemo {static Object objectLock new Object();public void m1(){synchronized (objectLock){System.out.println(----hello clearDemo);}}public static void main(String[] args) {LockClearDemo lockClearDemo new LockClearDemo();for (int i 0; i 10; i) {new Thread(() - {lockClearDemo.m1();},String.valueOf(i)).start();}}
}但此时做出这样一个修改 这么写看似有synchronized语法也没报错实际每个线程进来都new一个自己的object对象相当于是每一个线程一个自己创造的锁而不是正常的所有线程共同抢一个对象的锁因此这么写毫无意义JIT编译器会无视它极端的说就是根本没有加这个锁对象的底层的机器码是消除了锁的使用。 synchronized锁粗化 看示例代码
public class LockDemo {static Object objectLock new Object();public static void main(String[] args) {new Thread(() - {synchronized (objectLock){System.out.println(111111);}synchronized (objectLock){System.out.println(222222);}synchronized (objectLock){System.out.println(333333);}synchronized (objectLock){System.out.println(444444);}}).start();}}注意这不是可重入锁这里是频繁加锁解锁。虽然无语法错误但底层编译器会把它合并优化为 锁粗化假如方法中首尾相接前后相邻的都是同一个锁对象那JIT编译器就会把这几个synchronized块合并成一个大块加粗加大范围一次申请锁使用即可避免次次的申请和释放锁提升了性能。
11、结语
没有锁自由自在偏向锁唯我独尊轻量锁楚汉争霸重量锁群雄逐鹿