CAS
concurrent包下的类都有下面的实现模式
1、首先,声明变量为volatile
2、然后,使用CAS的原子条件更新来实现线程之间的同步
3、配合使用volatile的写可见性和CAS的原子性来实现线程之间的通信(线程安全)
最近在看 java.util.concurrent.atomic 包下的AtomicInteger源码发现它是利用CAS来实现原子操作、Volatile保证元素的可见性来实现无锁下的线程安全。 决定深入了解一下CAS
MySQL中的MVCC(多版本并发控制)中的乐观锁也是通过CAS机制和版本号实现无锁更新数据的
CAS:Campare And Swap 比较和交换,是一种无锁算法,在不使用锁的情况下实现多线程之间的变量同步。
CAS(V,E,N) 的三个参数
V 要修改的对象(内存地址)
E Expect 期望原值
N New 要修改为的新值
比较和交换的原子操作是通过CPU的cmpxchg指令来实现的。
流程:
1、获取要修改的变量的现有值
2、和期望值比较,是否相同
3、相同的话则将变量的值修改为新值并返回TRUE,否则什么都不做并返回FALSE。
另外,还有许多CAS操作时自旋的,即如果操作不成功,会一直重试,直到操作成功为止(如JUC包中类对变量的修改操作)。
如AtomicInteger:
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (java.util.concurrent.atomic.AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; /** * 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 {@code 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: 获取并操作内存的数据(根据内存偏移量)。
valueOffset: 存储value在AtomicInteger中的内存偏移量。
value: 存储AtomicInteger的int值,该属性需要借助volatile关键字保证其在线程间是可见的
a++的操作是非原子性的,它实际上是两个操作
1)将a的值加一
2)将加一后的值赋给a
因此在多线程环境下,可能存在下面的问题
1、在不用volatile修饰的情况下
线程1获取a的值为1,线程2取a的值为1,分别对其进行a++操作,之后a的b不是3人、而是2
2、在用volatile修饰的情况下
线程1获取a的值为1,线程2获取a的值为1
线程1在对a加一,但未将加一的值赋给a之前,线程2完成了对a的加一并赋值的操作,此时变量a的值为2
由于volatile修饰的a保证了可见性,此时线程1中的工作内存中的a值无效,重新从堆内存中获取变量a的值,然后完成将a的值赋值为2
两个线程分别对变量a进行加一操作后,变量a的值不是期望的3,而是2,这是由于加一和赋值的操作不是原子性导致的,所以并发包内提供的原子性的自增方法
AtomicInteger: /** * Atomically increments by one the current value. * * @return the updated value */ public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } Unsafe: 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的使用是受限的,只有授信的代码才能使用,即JDK库里面的内才可以使用(其帮助Java访问操作系统底层资源,使Java具有底层操作能力,可以提升运行效率)
private static final Unsafe theUnsafe;
// 私有化构造函数
private Unsafe() {
}
// 只有此方法可以获取Unsafe对象
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
但是可以通过使用反射来获取Unsafe对象
Class unsafeClass = Unsafe.class; try { Field f = unsafeClass.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }
Unsafe类的方法
/** * CAS * @param var1 要修改field的对象 * @param var2 对象中某field的偏移量 * @param var4 期望值 * @param var5 更新值 * @return true|false */ public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
获取给定的paramField的内存地址偏移量,这个值对于给定的field是唯一且固定不变的
public native int arrayBaseOffset(Class<?> var1);
CAS存在的三大问题
1、ABA问题
ABA:例如两个线程同时更新同一个数据,线程1操作比较快,先把A修改为了B,然后再修改为了A;
线程2读取到线程1两次修改后的值A和之前的期望值A比较,相同,则认为没有被修改过符合期望,接着修改为新值。
解决方案是引入版本的概念:如A1-B2-A3,这样,另一个线程读取到的A3和期望值A1虽然值相同但是版本不同,则不进行后续的操作。
JDK从1.5开始提供了AtomicStampedReference类来解决ABA问题,具体操作封装在compareAndSet()中。
compareAndSet()首先检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值
2、循环时间长开销大
CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销
3、只能保证一个共享变量的原子操作
对一个共享变量执行操作时,CAS能够保证原子操作,但是同时对多个共享变量操作时,CAS是无法保证操作的原子性的。
Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作
CAS如果保证原子性的?
CAS包含C和S两个操作,其能保证原子性是因为CAS是由底层CPU支持的原子操作(cmpxchg原子指令),其原子性是在硬件层面进行保证的。
END.