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 要修改为的新值
流程:
1、获取要修改的变量的现有值
2、和期望值比较,是否相同
3、相同的话则将变量的值修改为新值并返回TRUE,否则什么都不做并返回FALSE。
AtomicInteger: /** * 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); }
另外,还有许多CAS操作时自旋的,即如果操作不成功,会一直重试,直到操作成功为止。
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存在的问题 ABA问题
ABA:例如两个线程同时更新同一个数据,线程1操作比较快,先把A修改为了B,然后再修改为了A;
线程2读取到线程1两次修改后的值A和之前的期望值A比较,相同,则认为没有被修改过符合期望,接着修改为新值。
解决方案是引入版本的概念:如A1-B2-A3,这样,另一个线程读取到的A3和期望值A1虽然值相同但是版本不同,则不进行后续的操作。
AtomicInteger中的valueOffset就是解决ABA问题的,这个具体为什么,我还没看懂,
private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }
valueOffset表示的是变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的原值的
value是用volatile修饰的,这是非常关键的
CAS如果保证原子性的?
CAS包含C和S两个操作,其能保证原子性是因为CAS是由底层CPU支持的原子操作,其原子性是在硬件层面进行保证的。