乐观锁思想实现原理是一种无锁原理。CAS指令是实现无锁原理的方案之一。
1、CAS是什么?
CAS的全称是Compare And Swap,即比较交换。
其核心思想: CAS(V,E,N); V表示要更新的变量的内存位置,E表示更新变量的预期原值,N表示更新变量的更新值。
在当前线程中,如果要更新变量预期原值E等于当前内存位置中的值,就把更新该内存位置的值,更新后的值为N。如果不相等,说明当前内存位置的值被其他线程更新,当前线程就不再做任何操作。所以,每一次CAS操作时,线程会先去读一下当前值,然后进行一次CAS操作:把期望值(刚才读出来的数据)跟现在内存位置的数据作一个比较,如果比较成功就可以把新的值写入。
2、关于CAS的两个问题
问题1:CAS操作对比期望值与实际值失败后就重试的策略,会不会很浪费资源?
关于这个问题,CAS是抱着乐观的态度进行操作的。它认为自己有非常在的概率是能够成功完成当前操作的,所以在CAS看来,完不成便重试是一个小概率事件。
问题2:CAS操作,先读再比较,然后设置值,步骤这么多,会不会在步骤之间被其它线程干扰导致冲突,这里是不是一个bug?
这个担心纯属多余,因为CAS操作的整个过程是原子操作,它由一条cpu指令完成,并没有把取数据、比较、设置值写在3个cpu指令里。 这条cpu指令叫cmpxchg,它的逻辑如下:
if(accumulator == Destination){
ZF = 1;
Destination = Source;
} else {
ZF = 0;
accumulator = Destination;
}
看目标值是不是跟计算值相等,如果相等就设置一个跳转标志,并且把原始数据设置到目标里面去,否则跳转标志就不设了。所以,CAS它从指令层面来保证操作的可靠性。
3、调用CAS的Java无锁类AtomicInteger
AtomicInteger类的部分主要内容:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
private static final Unsafe unsafe = Unsafe.getUnsafe();
//字段value在当前内存的偏移量,通过位置偏移量,就能得到value在内存中的值。
private static final long valueOffset;
//先加载静态常量,然后执行静态代码块(按照静态代码块先后顺序执行),最后调用构造函数。
//静态代码块:获取字段value在内存的偏移量。
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
/**
* 这是一个非常重要的字段“value”,用关键词volatile修饰,它是其封装的数据类型,
* AtomicInteger类所有的操作基本都是对成员变量“value”所做,
* "value"才是它内部真正的值,AtomicInteger类只是对它的一个包装而已。
*/
private volatile int value;
/**
* Creates a new AtomicInteger with the given initial value.
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
/**
* Creates a new AtomicInteger with initial value {无参构造器}.
*/
public AtomicInteger() {
}
/**
* Gets the current value.
* 获取当前的value
* @return the current value
*/
public final int get() {
return value;
}
/**
* Sets to the given value.
* @param newValue the new value
*/
public final void set(int newValue) {
value = newValue;
}
/**
* Atomically sets to the given value and returns the old value.
* 设置一个新值并返回旧值,这是一个满足原子性的操作(Atomically)。
* @param newValue the new value
* @return the previous value
*/
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
/**
* Atomically sets the value to the given updated value if the current value {@code ==} the expected value.
* 参数expect是期望值,参数u是要设置的值,如果期望值等于当前值,即设置成功返回true,否则失败返回false;
* compareAndSet方法内部用到了一个unsafe对象,从名字可以看到它是一个不安全的操作。
* 我们知道,java相对于c++或者c更为安全是因为java把有关指针的一些内容给封装起来了或者说屏蔽掉了,而Unsafe类恰恰相反,
* 它会提供一些相对于java底层类似于指针的一些操作,比如这里的compareAndSwapInt方法,表示针对this对象,
* 偏移量为valueOffset的数据,看它的期望值是多少,这是一个非常有c感觉的一个操作。
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* <p><a href="package-summary.html#weakCompareAndSet">May fail
* spuriously and does not provide ordering guarantees</a>, so is
* only rarely an appropriate alternative to {@code compareAndSet}.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful
*/
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
//jdk 1.8.0_51
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//getAndAddInt 参数: var1 = this, var2 = valueOffset, var4 = delta
//compareAndSwapInt 参数:var1 = this, var2 = valueOffset, var5 = 旧值, var5 + var4 = 要修改成的值
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
//unsafe.getAndAddInt(this, valueOffset, delta);方法内部使用一个do_while循环,先得到当前的值var5,然后再把当前的值加var4,
//加完之后使用cas原子操作让当前值加var4处理正确。当然cas原子操作不一定是成功的,所以做了一个死循环,当cas操作成功的时候返回数据。
//这里由于使用了cas原子操作,所以不会出现多线程处理错误的问题。比如线程A得到var5为1,线程B也得到var5为1;线程A的var5 + var4值为2,
//进行cas操作并且成功的时候,将var5修改成了2;这个时候线程B得到var5 + var4值仍旧为2,当进行cas操作的时候由于内存中的值已经是2,
//而不是1了;所以cas操作会失败,下一次循环的时候得到的var5就变成了2;也就不会出现多线程处理问题了。
方法getAndIncrement()解释(jdk1.7版本之前):
//取得旧值,在当前值上+1,这是一个线程安全的操作;
public final int getAndIncrement(){
for(;;){
int current = get();
int next = current + 1;
if(compareAndSet(current,set))
return current;
}
}
//可以从其实现中看到,先用get方法取出当前值current,对其加1并赋值给next(这里注意,这个加1一定是对当前值加1,不可能是其它线程改过的数据),
//然后对当前值与+1后的值next进行比较,如果成功,直接返回current。如果在执行完加1之后,有其它线程先一步修改了当前数据,
//会导致实际的current与线程取到的current不相符,所以compareAndSet方法执行失败返回false,程序无法执行到return语句,
//直接回到循环体的开始再重试一遍,直到设置成功为止。从getAndIncrement方法的实现代码中,我们可以看到无锁的算法的基本实现形式,
//即套在一个死循环当中一直作重试,直到自己成功为止。这是一个非常通用的思路,getAndDecrement()、incrementAndGet()等方法也是这种实现思路。
4、实现CAS的Java无锁类Unsafe
Unsafe提供了一些不太安全的操作,它主要是在jdk内部使用,并不对外提供。如果你想要拿到Unsafe的实例,是需要动一些手脚的。它提供了如下操作:
根据偏移量去设置值;
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
这样就能通过unsafe对象拿到AtomicInteger在成员变量value上的偏移量。
如果说有写过c代码,这里就会比较好理解。
比如说,c里面有一个结构体,定义了两个int类型的成员a与b,如果a的偏移量是0,b的偏移量就是4,只要需要知道结构体所在的基地址(0x7fff0815c0e0),加上偏移量4就能得到b的地址(0x7fff0815c0e4=0x7fff0815c0e0+4),有了b的地址就能对b进行操作。回到java中,也是一样,只不过结构体换成了Class。Class会比c语言中结构体struct复杂一些,它还存有一些对象头部的信息,比如对象年龄等,然后才是字段,比如int value。这个objectFieldOffset方法它拿到的就是value字段在class中的偏移量,所以后面我们就可以根据这个偏移量去做一些设置,比如前方文中中的提到的unsafe.compareAndSwapInt(this,valueOffset,expect,update);就是对当前AtomicInteger对象在valueOffset偏移量的位置上做设置操作:我期望它是expect,更新成update。
5、java无锁类AtomicReference
和封装整数的AtomicInteger相比,AtomicReference封装的是一个对象,它是对封装对象的引用,使用这个对象的引用来对对象进行修改,就能够保证对象的线程安全。
AtomicReference是一个模板,可以用来封装任意类型的数据,它里面的实现与AtomicInteger非常类似,它有成员变量value,也有一个偏移量valueOffset,有get、set方法,也有compareAndSet方法。
如果你有一个对象,希望在修改时保证该对象是线程安全的,就可以使用AtomicReference来包装它。
更详细解释:http://www.hao124.net/article/52
更多原子类:Java并发编程-无锁CAS与Unsafe类及其并发包Atomic
参考:
http://www.hao124.net/article/52