再有人说synchronized是重量级锁,就把这篇文章扔给他看( 二 )


2.3 锁消除JVM在JIT编译时通过对运行上下文的扫描,经过逃逸分析 , 对于某段代码不存在竞争或共享的可能性,就会讲这段代码的锁消除 , 提升程序运行效率 。
public void method() {final Object LOCK = new Object();synchronized (LOCK) {// do something}}比如上面代码中锁,是方法中私有的,又是不可变的 , 完全没必要加锁,所以JVM就会执行锁消除 。
2.4 锁粗化按理来说 , 同步块的作用范围应该尽可能小,仅在共享数据的实际作用域中才进行同步,这样做的目的是为了使需要同步的操作数量尽可能缩?。醵套枞奔洌绻嬖谒赫? ,那么等待锁的线程也能尽快拿到锁 。但是加锁解锁也需要消耗资源,如果存在一系列的连续加锁解锁操作,可能会导致不必要的性能损耗 。

锁粗化就是将多个连续的加锁、解锁操作连接在一起 , 扩展成一个范围更大的锁,避免频繁的加锁解锁操作 。
public void method(Object LOCK) {synchronized (LOCK) {// do something1}synchronized (LOCK) {// do something2}}比如上面方法中两个加锁的代码块,完全可以合并成一个,减少频繁加锁解锁带来的开销,提升程序运行效率 。
2.5 偏向锁为什么要引入偏向锁?
因为经过HotSpot的作者大量的研究发现,大多数时候是不存在锁竞争的,通常是一个线程多次获得同一把锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁 。
2.6 轻量级锁轻量级锁考虑的是竞争锁对象的线程不多 , 而且线程持有锁的时间也不长的场景 。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋(CAS)这等待锁释放 。
加锁过程:当代码进入同步块时,如果同步对象为无锁状态时,当前线程会在栈帧中创建一个锁记录(Lock Record)区域,同时将锁对象的对象头中 Mark Word 拷贝到锁记录中,再尝试使用 CASMark Word 更新为指向锁记录的指针 。如果更新成功,当前线程就获得了锁 。
解锁过程:轻量锁的解锁过程也是利用 CAS 来实现的,会尝试锁记录替换回锁对象的 Mark Word。如果替换成功则说明整个同步操作完成 , 失败则说明有其他线程尝试获取锁,这时就会唤醒被挂起的线程(此时已经膨胀为重量锁)
2.7 重量级锁synchronized是通过对象内部的监视器锁(Monitor)来实现的 。但是监视器锁本质又是依赖于底层的操作系统的互斥锁(Mutex Lock)来实现的 。
重量级锁的工作流程:当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cpu 。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙 , 这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长 , 所以重量级锁的开销还是很大的 。
在锁竞争激烈、锁持有时间长的场景,还是适合使用重量级锁的 。
2.8 锁升级过程
再有人说synchronized是重量级锁,就把这篇文章扔给他看

文章插图
2.9 锁的优缺点对比锁的性能从低到高,依次是无锁、偏向锁、轻量级锁、重量级锁 。不同的锁只是适合不同的场景,大家可以依据实际场景自行选择 。
再有人说synchronized是重量级锁,就把这篇文章扔给他看

文章插图
3. 总结synchronized锁经过多次迭代优化,已经不像以前那么重了,在JDK1.8的ConcurrentHashMap源码中已经大量使用synchronized做同步控制 , 大家在日常开发中可以放心使用了 。
再有人说synchronized是重量级锁,就把这篇文章扔给他看

文章插图

推荐阅读