话说这个synchronized关键字保证了,多线程环境下加锁 强行-单线程执行。可惜呀,这个性能太差劲了,因此就有了 CAS一种无锁算法的出现。
我们今天从 java.util.concurrent.atomic.AtomicInteger 并发包下的这个工具类慢慢道来
AtomicInteger atomicInteger=new AtomicInteger(5);
atomicInteger.compareAndSet(5,2);
public final boolean compareAndSet(int expect, int update);
预期值,更新值
CAS— compareAndSet,比较并交换。这个又牵扯到了前面的JMM。上图 加深理解
从源码接口认识几个概念,预期值,更新值
结合图片,啥是预期值?线程工作内存的值—如果这个值和主内存的值相同,就修改为更新值、compareAndSet 返回true。
那么问题来了,线程t1执行compareAndSet 操作的时候不怕其他线程打扰吗?接着往下看…
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
//this 当前对象,valueOffset 内存偏移地址--直接定位内存。
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
// valueOffset 内存偏移量
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value; //线程可见
通过追踪代码,发现直接调到了 unsafe类的compareAndSwapInt 方法上,这是一个native方法。
1、被native关键字修饰的方法叫做本地方法
2、一个Native Method就是一个java调用非java代码的接口
3、该方法的实现由非java语言实现,比如C,汇编 基于硬件操作。
总的来说,unsafe 是入口,他的 native 修饰的方法是原子操作的,直接操作底层系统-内存、不会被其他线程打断。
这一切都得益于 unsafe 这个类,我们认识一下,位于jdk— rt.jar、package sun.misc.Unsafe;
public final class Unsafe {
private static final Unsafe theUnsafe;
public static final int INVALID_FIELD_OFFSET = -1;
public static final int ARRAY_BOOLEAN_BASE_OFFSET;
public static final int ARRAY_BYTE_BASE_OFFSET;
public static final int ARRAY_SHORT_BASE_OFFSET;
public static final int ARRAY_CHAR_BASE_OFFSET;
public static final int ARRAY_INT_BASE_OFFSET;
public static final int ARRAY_LONG_BASE_OFFSET;
public static final int ARRAY_FLOAT_BASE_OFFSET;
public static final int ARRAY_DOUBLE_BASE_OFFSET;
public static final int ARRAY_OBJECT_BASE_OFFSET;
public static final int ARRAY_BOOLEAN_INDEX_SCALE;
public static final int ARRAY_BYTE_INDEX_SCALE;
public static final int ARRAY_SHORT_INDEX_SCALE;
public static final int ARRAY_CHAR_INDEX_SCALE;
public static final int ARRAY_INT_INDEX_SCALE;
public static final int ARRAY_LONG_INDEX_SCALE;
public static final int ARRAY_FLOAT_INDEX_SCALE;
public static final int ARRAY_DOUBLE_INDEX_SCALE;
public static final int ARRAY_OBJECT_INDEX_SCALE;
public static final int ADDRESS_SIZE;
private static native void registerNatives();
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
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);
public native Object getObjectVolatile(Object var1, long var2);
public native void putObjectVolatile(Object var1, long var2, Object var4);
总算摸清楚了,关键之处。。。。累吐血,接着我们探索一下另一个问题。针对i++ 我们经常用 atomicInteger.incrementAndGet(); 代替,说什么自旋–无并发,今天借着这个机会看一下。
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
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));
//oh,my god 原来此处,每次都会从主内存取最新值,和线程自己工作内存中对比--简称自旋、否则不会跳出循环。。。。。
return var5;
}
jj,一个复杂的问题 终于被阐述清楚了…
任何事物都有两面性,CAS也不例外…
ABA 后续补充。。。。
上面引出了ABA这个问题,今天接着细细研究一下。有这样一种场景…
t1线程执行慢,t2线程执行快–t2 执行期间主物理内存值从A->B->A, t1进行CAS时拿到的还是A,期间的变化未知
因为 CAS是取出内存某个时刻的数据比较替换,无法感知时间差导致的数据变化、可能会导致一些问题。上图解释
t1执行2秒,t2执行10秒,t1 把主内存的值由 A->B->A,t2 进行CAS时拿到的还是A,期间的变化不能感知。
针对这个问题如何解决?版本号–乐观锁,
java.util.concurrent.atomic.AtomicStampedReference 使用这个工具类即可,
自定义原子对象,
AtomicStampedReference<Integer> stampedReference=new AtomicStampedReference(100,1);
new Thread(()->{
int stamp=stampedReference.getStamp();
System.out.println("t3:"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
}
catch (InterruptedException e) {
e.printStackTrace();
}
stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
System.out.println("t3 更新后版本号:"+stampedReference.getStamp());
},"t3").start();
new Thread(()->{
int stamp=stampedReference.getStamp();
System.out.println("t4:"+stamp);
try {
TimeUnit.SECONDS.sleep(3);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t4 工作线程版本号:"+stamp+"——>主内存版本号:"+stampedReference.getStamp());
System.out.println("t4:"+stampedReference.compareAndSet(100,120,stamp,stamp+1)+"当前值:"+atomicInteger.get());
},"t4").start();
//执行结果:
t4:1
t3:1
t3 更新后版本号:3
t4 工作线程版本号:1——>主内存版本号:3
t4:false当前值:100