Synchonized原理

总述:先来看下利用synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。

  1. ·对于普通同步方法,锁是当前实例对象。
  2. ·对于静态同步方法,锁是当前类的Class对象。
  3. ·对于同步方法块,锁是Synchonized括号里配置的对象

一、从JVM规范中可以看到Synchonized在JVM里的实现原理,JVM基于进入和退出Monitor对

象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter

和monitorexit指令实现的,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有

详细说明。但是,方法的同步同样可以使用这两个指令来实现

二、monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结

束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有

一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter

指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

三、Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在

Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状

态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏

向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高

获得锁和释放锁的效率

四、原子性

原子(atomic)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意

为“不可被中断的一个或一系列操作”。在多处理器上实现原子操作就变得有点复杂。让我们

一起来聊一聊在Intel处理器和Java里是如何实现原子操作的。

4.1处理器如何实现原子操作

(1)使用总线锁保证原子性

(2)使用缓存锁保证原子性

缓存对处理器的影响更小,效率更高,但是有两种情况下处理器不会使用缓存锁定。

第一种情况是:当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行

(cache line)时,则处理器会调用总线锁定。

第二种情况是:有些处理器不支持缓存锁定。对于Intel 486和Pentium处理器,就算锁定的

内存区域在处理器的缓存行中也会调用总线锁定。

4.2Java如何实现原子操作

(1)使用循环CAS实现原子操作

JVM中的CAS操作正是利用了处理器提供的CMPXCHG指令实现的。自旋CAS实现的基本

思路就是循环进行CAS操作直到成功为止,

(2)CAS实现原子操作的三大问题

1)ABA问题:因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化

则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它

的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面

追加上版本号,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A。从

Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个

类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是

否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值

2)循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。

3)只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循

环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子

性,这个时候就可以用锁。

(3)使用锁机制实现原子操作

锁机制保证了只有获得锁的线程才能够操作锁定的内存区域。JVM内部实现了很多种锁

机制,有偏向锁、轻量级锁和互斥锁。有意思的是除了偏向锁,JVM实现锁的方式都用了循环

CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时

候使用循环CAS释放锁。

参考资料  并发编程的艺术  写的非常好   推荐

猜你喜欢

转载自blog.csdn.net/yang_154116/article/details/81173554