悲观锁和乐观锁
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算法是怎样体现的。
/**
* 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;
}
}
/**
* 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);
}
下面是Unsafe类中的compareAndSwapInt方法
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
注意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处理器的源代码的片段:// 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
}
}
如上面源代码所示,程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg)。反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果)。
CAS算法在硬件中的实现是cmpxchg指令,保证这个指令的原子性。执行这个指令时,会通过CPU锁来实现的原子性。java在1.5之后其实是提供了调用硬件cmpxchg指令的方法(Unsafe类中那个native CAS方法)而已。
intel的手册对lock前缀的说明如下:
总结
使用CAS可以提高性能,但是CAS同样也存在一些问题,比如说ABA的问题。使用CAS可以实现无“锁”编程。