(3)锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。这几个状态会随着竞争情况逐渐升级。锁可以升级但是不能降级。
偏向锁:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要测试下对象头的Mark Word里是否存储着指向当前线程的偏向锁。测试成功那么线程已经获得了锁,测试失败,会在测试下Mark Word中偏向锁的标识是否置为1(表示当前是偏向锁),如果设置了,尝试使用CAS将对象头的偏向锁指向当前线程,没有设置,就使用CAS竞争锁。
偏向锁的撤销:偏向锁使用了一种等到竞争出现才释放锁的机制,当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。
注意:JVM参数可以控制关闭激活偏向锁的延迟和关闭偏向锁。
轻量级锁:CAS修改Mark Word,成功的话,当前线程获得锁,失败表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
轻量级锁释放:CAS替换Mark Word,成功的话,没竞争。失败的话,表示存在竞争,锁会膨胀为重量级锁。
(4)锁的优缺点对比
偏向锁:
优点:加锁和解锁不需要额外的消耗;
缺点:如果线程间存在锁竞争,会带来额外的锁撤销的消耗;
适用场景:适用于只有一个线程访问同步块场景。
轻量级锁:
优点:竞争的线程不会阻塞,提高了程序的响应速度;
缺点:如果始终得不到锁竞争的线程,使用自旋会消耗CPU;
适用场景:追求响应时间,同步块执行速度非常快。
重量级锁:
优点:线程竞争不使用自旋,不会消耗CPU;
缺点:线程阻塞,响应时间缓慢;
适用场景:追求吞吐量,同步块执行速度较长。
原子操作指的是不可中断的一个或一系列操作。CAS操作需要输入两个数值,一个旧值一个新值,在操作期间先比较旧值有没有发生变化,如果没有发生变化,才变换成新值,发生了变化则不交换。
处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。总线锁是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住。总线锁定把CPU和内存之间的通信锁住了,这样锁定期间其他处理器不能操作其他内存地址的数据,处理器在某些场合下会使用缓存锁定代替总线锁定来进行优化。
4、Java如何实现原子操作Java中可以通过锁和循环CAS的方式来实现原子操作。CAS存在三大问题:ABA问题、循环时间长开销大、只能保证一个共享变量的原子操作。
(1)ABA问题:因为CAS需要在操作值的时候,检查值有没有变化,如果没有变化就更新。但是如果原来的值A,变成了B,又变成了A。那么使用CAS检查时会发现它的值没有变化,实际上是变化了的。ABA问题的解决思路是使用版本号,在变量前追加版本号,每次变量更新的时候把版本号加1。
(2)循环时间长开销大
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
(3)只能保证一个共享变量的原子操作
对多个共享变量操作时,循环CAS无法保证操作的原子性。Java1.5开始,JDK提供了AtomicReference类保证引用对象之间的原子性,可以把多个变量放在一个对象里进行CAS操作。