一文搞懂synchronized原理

原理

众所周知 synchronized 关键字是解决并发问题经常使用解决方案,有如下三种使用方式:java

  • 同步普通方法,锁的是当前对象。
  • 同步静态方法,锁的是当前 Class 对象。
  • 同步块,锁的是 () 中的对象。

实现原理: JVM 是经过进入、退出对象监视器( Monitor )来实现对方法、同步块的同步的。
具体实现是在编译以后在同步方法调用前加入一个 monitor.enter 指令,在退出方法和异常处插入 monitor.exit 的指令。
其本质就是对一个对象监视器( Monitor )进行获取,而这个获取过程具备排他性从而达到了同一时刻只能一个线程访问的目的。
而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 monitor.exit 以后才能尝试继续获取锁。安全

经过一段代码来演示:多线程

使用 javap -c Synchronize 能够查看编译以后的具体信息。并发

这里插入一下,推荐一个在IDEA中快速使用Javap的方法。性能

步骤优化

使用方法

添加以后选中类而后运行,就会把字节码打印到控制台,很是方便线程

锁优化

synchronized 不少都称之为重量锁,JDK1.6 中对 synchronized 进行了各类优化,为了能减小获取和释放锁带来的消耗引入了偏向锁轻量锁3d

轻量锁

当代码进入同步块时,若是同步对象为无锁状态时,当前线程会在栈帧中建立一个锁记录(Lock Record)区域,同时将锁对象的对象头中 Mark Word 拷贝到锁记录中,再尝试使用 CASMark Word 更新为指向锁记录的指针。指针

若是更新成功,当前线程就得到了锁。code

若是更新失败 JVM 会先检查锁对象的 Mark Word 是否指向当前线程的锁记录。

若是是则说明当前线程拥有锁对象的锁,能够直接进入同步块。

不是则说明有其余线程抢占了锁,若是存在多个线程同时竞争一把锁,轻量锁就会膨胀为重量锁

解锁

轻量锁的解锁过程也是利用 CAS 来实现的,会尝试锁记录替换回锁对象的 Mark Word 。若是替换成功则说明整个同步操做完成,失败则说明有其余线程尝试获取锁,这时就会唤醒被挂起的线程(此时已经膨胀为重量锁)

轻量锁能提高性能的缘由是:

认为大多数锁在整个同步周期都不存在竞争,因此使用 CAS 比使用互斥开销更少。但若是锁竞争激烈,轻量锁就不但有互斥的开销,还有 CAS 的开销,甚至比重量锁更慢。

偏向锁

为了进一步的下降获取锁的代价,JDK1.6 以后还引入了偏向锁。

偏向锁的特征是:锁不存在多线程竞争,而且应由一个线程屡次得到锁。

当线程访问同步块时,会使用 CAS 将线程 ID 更新到锁对象的 Mark Word 中,若是更新成功则得到偏向锁,而且以后每次进入这个对象锁相关的同步块时都不须要再次获取锁了。

释放锁

当有另一个线程获取这个锁时,持有偏向锁的线程就会释放锁,释放时会等待全局安全点(这一时刻没有字节码运行),接着会暂停拥有偏向锁的线程,根据锁对象目前是否被锁来断定将对象头中的 Mark Word 设置为无锁或者是轻量锁状态。

偏向锁能够提升带有同步却没有竞争的程序性能,但若是程序中大多数锁都存在竞争时,那偏向锁就起不到太大做用。可使用 -XX:-UseBiasedLocking 来关闭偏向锁,并默认进入轻量锁。

其余优化

适应性自旋

在使用 CAS 时,若是操做失败,CAS 会自旋再次尝试。因为自旋是须要消耗 CPU 资源的,因此若是长期自旋就白白浪费了 CPUJDK1.6加入了适应性自旋:

若是某个锁自旋不多成功得到,那么下一次就会减小自旋。

原文地址:https://crossoverjie.top/JCSprout/#/thread/Synchronize

相关文章
相关标签/搜索