volatile实现原理
见 http://www.jianshu.com/p/d577c2817af8
synchronized
重量级锁,将会引发线程阻塞,线程切换,但是从Java SE 1.6对synchronized进行了各种优化后,性能上提升了不少。synchronized是可重入锁
1-锁的三种形式
Java中的每一个对象(类对象,和类的实例对象)都可以作为锁
- 对于普通同步方法:锁是当前实例对象。
- 对于静态同步方法:锁的是当前类的Class对象。
- 对于同步方法块:锁的是synchronized(){}中()中包含的对象。
JVM规范中,synchronized的实现:同步方法是通过方法表中的方法访问标志中是否含有ACC_SYNCHRONIZED标志来实现的;而同步方法块是在方法块的开始和结束分别插入monitorenter和monitorexit来完成的。
2-Java对象头与锁
在堆上创建对象时会设置对象头,详见
在32位JVM中对象头中和锁相关的有以下数据:锁状态,是否偏向锁,锁标志位
锁一共有4中状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,锁的状态只能升级不能降级,提高了获取和释放锁的效率。
偏向锁使用CAS竞争锁,轻量级锁使用CAS将对象头中的Mark Work替换为指向线程帧栈锁记录的指针,这两种锁都不会使得竞争的线程阻塞,而重量级锁将会阻塞竞争锁的线程
3-监视器的获取
无论是同步方法块还是同步方法,本质都是对一个对象的监视器(monitor)进行获取,这个获取过程是排他的,也就是说同一时刻只有一个线程获取到由synchronized所保护对象的监视器。
任意一个对象都拥有自己的监视器对象,当这个对象由同步方法或方法块调用时,执行方法的线程必须先获取到该对象的监视器才能进入到同步块或方法,而没有获取到监视器(执行该方法)的线程将会被阻塞在同步块或方法的入口处,进入阻塞状态。
CAS
比较并替换,具有和volatile读和写的内存语义。
JVM中CAS操作利用了处理器提供的CMPXCHG指令配合LOCK指令实现的,这个指令操作是原子性的
java.util.concurrent.atomic包下的原子操作类都是基于CAS实现的,接下去我们通过AtomicInteger来看看是如何通过CAS实现原子操作的:
-
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
valueOffset表示的变量对象的value域在内存中的偏移地址,Unsafe就是根据内存偏移地址获取数据的原值的。 -
unsafe.getAndAddInt(this, valueOffset, delta);
这个函数将指定偏移地址上的int数据域返回并原子性地增加delta
CAS的内存语义
CAS操作具有volatile读和写的内存语义,编译器禁止对CAS与CAS前面和后面的任意内存操作进行重排序。
** 为什么会有这样的内存语义? **
** 主要取决于LOCK指令 **。在单核处理器中,所有操作都是串行进行的,CMPXCHG指令前面没有LOCK指令,但是在多核处理器中,CMPXCHG指令前面会有LOCK指令。而这个LOCK指令在现在处理器中采用的是锁缓存来保证指令执行的原子性,并禁止与之前之后读写指令重排,还会把写缓存区中的所有数据刷新到内存中,而这正好和volatile的读和写的内存语义一致(不管是底层机制还是表现效果)
使用CAS会出现ABA问题
解决思路就是使用版本号:A→B→A 变为 1A→2B→3A,JDK的Atomic包提供的AtomicStampedReference来解决ABA问题。