lock前缀的指令会在多核处理器下引发两件事情:
1、将当前处理器的缓存行的数据写会到系统内存。lock前缀指令导致在执行指令期间,声明处理器lock#信号,在多处理器环境中,lock#信号确保在声明该信号期间,处理器可以
独享内存。在最近的处理器中,lock#信号一般不锁总线,而是锁缓存。
2、这个写会内存的操作会使在其他cpu里的缓存了该内存地址的数据无效。
处理器会使用嗅探技术保证它的内部缓存、系统内存和其他处理器缓存的数据在总线上保持一致---缓存一致性原则
synchronized的实现原理与应用
java中每一个对象都可以作为锁,具体表现在一下三种形式:1、对于普通同步方法,锁是当前实例对象
2、对于静态同步方法,锁是当前类的Class对象
3、对于同步方法块,锁是synchronized括号里配置的对象。
jvm基于进入和退出Monitor对象来实现同步和代码块同步,但两者的实现细节不一样,代码块同步使用monitorenter和monitorexit
指令实现。monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处。jvm要保证
每个monitorenter必须有对应的monitorexit与之对应。
锁一共有4中状态
级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。这几个状态会随着竞争情况逐渐升级。锁可以升级但是不能降级,以为这偏向锁升级成轻量级锁后将不能降级为偏向级锁。
偏向锁
偏向锁加锁:当一个线程访问同步块并获得锁时,会在对象头和栈帧中的锁记录里存储偏向锁的线程ID,以后该线程在进入和退出同步块时不需要
进行CAS操作来加锁和解锁,只需要简单的测试一下对象头的Mark Work里是否存储着指向当前线程的偏向锁。如果测试成功,表示已经
获得了锁,如果测试失败,则需要在测试一下Mark Word 中偏向锁的标识是否设置成1:如果没有设置,则使用CAS竞争锁;如果设置了
则尝试使用CAS将对象头的偏向锁指向当前线程。
偏向锁撤销:
它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否还活着,如果线程不处于活动状态,则将对象设置成无锁状态,如果
如果线程仍然状态,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象的Mark Word要么重新偏向于其他线程,要么
恢复到无锁或者标记对象不合适作为偏向锁。
轻量级锁
轻量级锁加锁线程在执行同步块之前,JVM会先在当前线程栈帧中创建用于存储锁记录的空间,并将对象中的Mark Wrod复制到锁记录中。
轻量级锁解锁
轻量级解锁时,会使用原子的CAS操作将Mark Word 替换回到对象头,如果成功,则表示没有竞争发生,如果失败,表示当前锁存在
竞争,锁会膨胀成重量级锁。
处理器如何实现原子操作
使用总线锁保证原子性
1、第一个机制是通过总线锁保证原子性:处理器使用总线锁来解决这个问题。所谓总线锁就是使用处理器提供的一个lock#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占共享内存。
使用缓存锁保证原子性
2、第二个机制是通过缓存锁保证原子性:所谓“缓存锁定”是指内存区域如果被缓存在处理器的缓存行中,并且在lock操作期间被锁定,那么
当它执行锁操作回写到内存时,处理器不在总线上声明lock#信号,而是修改内部的内存地址,而是修改内存的内存地址,并允许他的缓存一
一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改有两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定
的缓存行数据时,会使缓存行无效。
CAS实现原子操作的三大问题
1、ABA问题因为CAS需要在操作值得时候,检查值有没有发生变化,如果没有发生变化,则更新,但是如果一个值原来是A,编程了B,又编程了A,那么使用
CAS进行检查时会发现他的值没有发生变化,但是实际上已经发生了变。ABA问题的解决思路就是使用版本号,在变量前面追加上版本号,每次
变量更新的时候把版本号加1.
2、循环时间长开销大
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销,如果JVM支持处理器提供的pause指令,那么效率会有一定的提升,pause指令有两个
作用:第一,它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟时间取决于具体实现的版本,在一些处理器上
延迟时间是零。第二,他可以避免在退出循环的时候因内存顺序冲突而引起CPU流水线被清空,从而提高CPU的执行效率。
3、只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性
这个时候就可以使用锁,还有一个取巧的方法,就是把多个共享变量合并成一个共享变量来操作。eg:i=2,j=a,合并一下ij=2a;