通常情况下,共享变量在并发中的处理是使用synchronized
关键字或lock
,
xxxxxxxxxx
// 共享变量
static int count = 0;
// 并发方法
public static synchronized boolean request() {
count++;
}
但是这种方式效率过低,是否存在一种更高效的处理方式:在修改共享变量之前,判断期望值和当前值,如果不一致则一直等待,如果相等那么修改共享变量。新的方式只需要在判断逻辑中加锁
xxxxxxxxxx
// volatile保证可见性,避免拿到缓存
volatile static int count = 0;
/**
* 判断是否更新
* @param expectCount 期望值count
* @param newCount 赋新值
* @return 是否成功
*/
public static synchronized boolean compareAndSwap(int expectCount, int newCount) {
// 判断当前count和期望值是否一致
if (getCount() == expectCount) {
count = newCount;
return true;
}
return false;
}
public static int getCount() {
return count;
}
public static void request(){
int expectCount;
// 判断和赋值
while(!compareAndSwap((expectCount=getCount()),expectCount+1)){}
}
CAS全称是CompareAndSwap
,比较并替换
CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作
CAS是通过JNI借助C语言实现的,例如指令cmpxchg
系统底层进行CAS操作时,会判断当前系统是否是多核心系统,会给总线加锁,然后执行CAS操作
CAS的问题:高并发情况下存在性能问题
如果存在以下的情况: 并发1:获取出数据的初始值是A,后续计划实施CAS,期望数据还是A,新值为B 并发2:将数据修改成B再修改回A 并发1:CAS检测发现被修改后的数据是A,修改为B
或者可以理解为丢失了一个版本更新
简单地模拟
xxxxxxxxxx
public static AtomicInteger a = new AtomicInteger(1);
public static void main(String[] args) {
Thread main = new Thread(new Runnable() {
public void run() {
System.out.println("操作线程:"+Thread.currentThread().getName());
System.out.println("初始值:"+a.get());
try {
int expectNum = a.get();
int newNum = expectNum+1;
Thread.sleep(1000);
boolean isCASSuccess = a.compareAndSet(expectNum,newNum);
System.out.println("操作线程:"+Thread.currentThread().getName());
System.out.println("CAS是否成功:"+isCASSuccess);
}catch (Exception e){
e.printStackTrace();
}
}
},"主线程");
Thread other = new Thread(new Runnable() {
public void run() {
try {
// 确保主线程优先执行
Thread.sleep(20);
// a++
a.incrementAndGet();
System.out.println("操作线程:"+Thread.currentThread().getName());
System.out.println("increment:"+a.get());
// a--
a.decrementAndGet();
System.out.println("操作线程:"+Thread.currentThread().getName());
System.out.println("decrement:"+a.get());
}catch (Exception e){
e.printStackTrace();
}
}
},"干扰线程");
main.start();
other.start();
}
打印如下,主线程并没有感受到这个值的变化
xxxxxxxxxx
操作线程:主线程
初始值:1
操作线程:干扰线程
increment:2
操作线程:干扰线程
decrement:1
操作线程:主线程
CAS是否成功:true
问题解决:
对上述案例的解决
xxxxxxxxxx
// 第一个参数是数据,第二个参数是初始化版本
public static AtomicStampedReference<Integer> a = new AtomicStampedReference<>(1, 1);
public static void main(String[] args) {
Thread main = new Thread(new Runnable() {
public void run() {
System.out.println("操作线程:" + Thread.currentThread().getName());
System.out.println("初始值:" + a.getReference());
try {
Integer expectReference = a.getReference();
Integer newReference = expectReference + 1;
int expectStamp = a.getStamp();
int newStamp = expectStamp + 1;
Thread.sleep(1000);
boolean isCASSuccess = a.compareAndSet(expectReference, newReference, expectStamp, newStamp);
System.out.println("操作线程:" + Thread.currentThread().getName());
System.out.println("CAS是否成功:" + isCASSuccess);
} catch (Exception e) {
e.printStackTrace();
}
}
}, "主线程");
Thread other = new Thread(new Runnable() {
public void run() {
try {
// 确保主线程优先执行
Thread.sleep(20);
// a++
a.compareAndSet(a.getReference(), a.getReference() + 1, a.getStamp(), a.getStamp() + 1);
System.out.println("操作线程:" + Thread.currentThread().getName());
System.out.println("increment:" + a.getReference());
// a--
a.compareAndSet(a.getReference(), a.getReference() - 1, a.getStamp(), a.getStamp() + 1);
System.out.println("操作线程:" + Thread.currentThread().getName());
System.out.println("decrement:" + a.getReference());
} catch (Exception e) {
e.printStackTrace();
}
}
}, "干扰线程");
main.start();
other.start();
}
成功解决
xxxxxxxxxx
操作线程:主线程
初始值:1
操作线程:干扰线程
increment:2
操作线程:干扰线程
decrement:1
操作线程:主线程
CAS是否成功:false
JDK在sun.misc.unsafe
类中实现
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);
var1:表示要操作的对象(比较并替换的对象) var2:表示要操作对象中属性地址的偏移量 var4:修改数据的期望值 var5:需要修为的新值