Java并发-synchronized与原子操作的实现原理

synchronized

Java中的每一个对象都可以作为锁
1.对于普通同步方法,锁是当前实例对象
2.对于静态同步方法,锁是当前类的Class对象
3.对于同步方法块,锁是Synchronized括号里配置的对象
当一个线程驶入访问同步块时,它首先必须得到锁。退出或者抛出异常时必须释放锁。

实现原理

JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但实现细节不一样。
代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的。方法的同步同样可以使用这两个指令来实现。

monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象对应的monitor的所有权,即尝试获得对象的锁。

Java对象头

Java对象头里的Mark Word里默认存储对象的HashCode,分代年龄和锁标记位

锁的升级与对比

锁一共有4种状态:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态
锁可以升级但不能降级。这种锁升级却不能降级的策略,是为了提高获得锁和释放锁的效率

1.偏向锁:
大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来枷锁和解锁,只需要简单地测试以下对象头的MarkWord里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要在测试下MarkWord中偏向锁的标识是否设置成1(表示当前时偏向锁);如果没有设置,则使用CAS竞争锁,如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

偏向锁在Java6和Java7里默认启用的,但是它在应用程序启动几秒钟之后才激活。可以使用JVM参数来关闭延迟-XX:BiasedLockingStartupDelay=0.如果确定应用程序里所有的锁通常情况下处于竞争状态,可以JVM参数关闭偏向锁-XX:-UseBiasedLocking=false.那么程序默认会进入轻量级锁状态

2.轻量级锁
轻量级加锁,线程在执行同步块之前,JVM会现在当前线程的栈帧中创建用于存储记录的空间,并将对象头中的MarkWord复制到锁记录中。然后线程尝试使用CAS将对象头中的MarkWord替换为指向锁记录的执政。如果成功,获得锁,失败,表示其他线程竞争锁,当前鲜橙该便尝试使用自旋来获取锁。
轻量级解锁,使用CAS替换回到对象头,如果成功,则表示没有发生竞争,失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。

原子操作的实现原理

缓存行(Cache line)缓存的最小操作单位
比较并交换(Compare and Swap)操作期间旧值没有发生变化,才交换成新值
CPU流水线(CPU pipeline)CPU流水线的工作方式就像工业生产上的装配流水线,在CPU中由5-6个不同功能的电路单元组成一条指令处理流水线,然后将一条X86指令分成5-6步后在由这些电路单元分别执行,这样就能实现在一个CPU时钟周期完成一条指令,因此提高CPU的运算速度
内存顺序冲突(Memory order violation)内存顺序冲突一般是由假共享引起的,假共享指的就是多个CPU同时修改同一个缓存行的不同部分而引起其中一个CPU的操作无效,当出现这个内存顺序冲突时,CPU必须清空流水线

处理器提供总线锁定和缓存锁定来保证复杂内存操作的原子性
总线锁(处理器提供的一个Lock#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞,那么处理器可以独占共享内存)
缓存锁(在同一时刻,我们只需要保证对某个内存地址的操作是原子性即可,但是总线锁定把CPU和内存之间的通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销比较大。指内存区域如果被缓存在处理器的缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上LOCK#信号,而是修改内部内存地址,并循序它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会组织同时修改由两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时,会使缓存行无效)
2种情况必须使用总线
1.当操作的数据不能被缓存在处理器内部或操作的数据跨多个缓存行时,则处理器会调用总线锁定
2.有些处理器不支持缓存锁定

Java实现原子操作

1.使用循环CAS实现原子操作
JVM的CAS操作(自旋CAS实现的基本思路就是循环进行CAS操作知道成功为止)
2.CAS实现原子操作的三大问题
a.ABA问题(使用版本号解决)AtomicStampedReference
b.循环时间长开销大(如果CAS自旋长时间不成功,会给CPU带来非常大的执行开销)
c.只能保证一个共享变量的原子操作(可以用AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里进行CAS操作)

猜你喜欢

转载自blog.csdn.net/qq_29842929/article/details/80775250