悲观锁和乐观锁
synchronized的缺点:
在JDK1.5之前java语言是使用sychronized关键字实现同步,多线程并发。但是这样会有锁机制(对线程造成影响的,下面介绍的CAS其实也是有锁的,是硬件实现的锁),造成的问题如下:
CAS算法
CAS,英文全称是Compare and Swap,比较和交换,CAS算法属于乐观锁。在java.util.concurrent包中借助CAS实现了区别于synchronized悲观锁的一种乐观锁。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
下面通过java.util.concurrent包下的AtomicInteger来看看CAS算法是怎样体现的。
<span style="font-size:18px;">/** * Atomically sets to the given value and returns the old value. * * @param newValue the new value * @return the previous value */ public final int getAndSet(int newValue) { for (;;) { int current = get(); if (compareAndSet(current, newValue)) return current; } }</span>
<span style="font-size:18px;">/** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * @param expect the expected value * @param update the new value * @return true if successful. False return indicates that * the actual value was not equal to the expected value. */ public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }</span>
下面是Unsafe类中的compareAndSwapInt方法
<span style="font-size:18px;">public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);</span>
注意getAndSet方法中的循环,当执行CAS失败后继续进行循环直到成功为止,这样的方式并没有把线程阻塞,这样确实比synchronized独占锁方式性能高很多。
下面看看CAS的原理:
CAS通过调用JNI的代码实现的。JNI:Java Native Interface为JAVA本地调用,允许java调用其他语言。
而compareAndSwapInt就是借助C来调用CPU底层指令实现的。下面从分析比较常用的CPU(intel x86)来解释CAS的实现原理。
Unsafe类中的那个本地方法在openjdk中依次调用的c++代码为:unsafe.cpp,atomic.cpp和atomicwindowsx86.inline.hpp。这个本地方法的最终实现在openjdk的如下位置:openjdk-7-fcs-src-b147-27jun2011\openjdk\hotspot\src\oscpu\windowsx86\vm\ atomicwindowsx86.inline.hpp(对应于windows操作系统,X86处理器)。下面是对应于intel x86处理器的源代码的片段:<span style="font-size:18px;">// Adding a lock prefix to an instruction on MP machine // VC++ doesn't like the lock prefix to be on a single line // so we can't insert a label after the lock prefix. // By emitting a lock prefix, we can define a label after it. #define LOCK_IF_MP(mp) __asm cmp mp, 0 \ __asm je L0 \ __asm _emit 0xF0 \ __asm L0: inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { // alternative for InterlockedCompareExchange int mp = os::is_MP(); __asm { mov edx, dest mov ecx, exchange_value mov eax, compare_value LOCK_IF_MP(mp) cmpxchg dword ptr [edx], ecx } }</span>
如上面源代码所示,程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg)。反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果)。
CAS算法在硬件中的实现是cmpxchg指令,保证这个指令的原子性。执行这个指令时,会通过CPU锁来实现的原子性。java在1.5之后其实是提供了调用硬件cmpxchg指令的方法(Unsafe类中那个native CAS方法)而已。
intel的手册对lock前缀的说明如下:
总结
使用CAS可以提高性能,但是CAS同样也存在一些问题,比如说ABA的问题。使用CAS可以实现无“锁”编程。