什么是CAS?
- CAS(Compare and swap),即比较并交换,原子性操作。
为什么需要CAS?
首先我们先开一份代码
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class Demo01 {
private static int count = 0;
public static void request() throws InterruptedException {
TimeUnit.MICROSECONDS.sleep(5);//休眠5毫秒
count++;
}
public static void main(String[] args) throws InterruptedException {
int threadeSize=100;
long startTime = System.currentTimeMillis();//记录开始时间
CountDownLatch countDownLatch = new CountDownLatch(threadeSize);
for(int i=0;i<threadeSize;i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
request();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown(); // 线程-1
}
}
});
thread.start();
}
countDownLatch.await();
long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+" 耗时 "+(endTime-startTime)+" ms"+" 请求了 "+ count+" 次");
}
}
代码中,设置了100个线程,每个线程对count 加10次 那么按照单线程的思想的话,结果应该是1000.
我们看下结果。
结果并不是1000,这是为什么呢??
== 因为 count++ 的操作不是原子性的==
根据jvm 执行引擎的规定 , count++ 分为3步 第一步 取出count的值记为Ra,第二步 将Ra的值 +1 记为Rb 第三步将Rb的值赋值给count。
怎么解决这个问题呢?
首先我们肯定知道有一个关键字 就是给对象加锁 让他们实现" 串行操作"。synchronized 关键字 会对对象加锁,每一次进入synchronized 的作用范围的对象只能有一个 ,只有当获取锁的对象释放锁了 才会让另外一个线程进入。
那么 我们将原来的代码改成这样
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class Demo01 {
private static int count = 0;
public static synchronized void request() throws InterruptedException {
TimeUnit.MICROSECONDS.sleep(5);//休眠5毫秒
count++;
}
public static void main(String[] args) throws InterruptedException {
int threadeSize=100;
long startTime = System.currentTimeMillis();//记录开始时间
CountDownLatch countDownLatch = new CountDownLatch(threadeSize);
for(int i=0;i<threadeSize;i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
request();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown(); // 线程-1
}
}
});
thread.start();
}
countDownLatch.await();
long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+" 耗时 "+(endTime-startTime)+" ms"+" 请求了 "+ count+" 次");
}
}
结果
- 我们发现结果是正确了1000次,但是耗时1075ms 会不会太长了?如果是更多的线程 那么不是需要等待更久的时间的嘛。 这不就和获取第二天的时间 直接用 TimeUnit.HOURS.sleep(24); 一样的效果嘛。
优化代码
思路
- 我们发现 count++ 是三步,第一步 取出count的值 我们没办法操作,第二步 count+1 我们也没办法操作,只有第三步,count = count+1 这个我们可以操作一下。
怎么实现CAS
CAS 不是 比较and 替换嘛 那我们就每次用getCount 和 理想中count应该是的值来比较,如果相等 就赋值新的 如果不等 就不操作。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class Demo03 {
private static volatile int count = 0;
// 增加volatile 关键字 保证可见性,每次getCount() 的值都是最新值。
public static void request() throws InterruptedException {
TimeUnit.MICROSECONDS.sleep(5);//休眠5毫秒
while (!compareAndSwap(getCount(),getCount()+1)); // 直到修改了 才退出循环
}
/**
*
* @param expectCount 期望值(期望count=多少)
* @param newCount 新值 (重新将count设置为多少)
* @return 成功返回True 失败返回False
*/
public synchronized static Boolean compareAndSwap(int expectCount,int newCount){
if(getCount() == expectCount ) {//如果get到的count 等于期望值 就赋值一个新的值
count = newCount;
return true;
}
return false;
}
public static int getCount(){
return count;
}
public static void main(String[] args) throws InterruptedException {
int threadeSize=100;
long startTime = System.currentTimeMillis();//记录开始时间
CountDownLatch countDownLatch = new CountDownLatch(threadeSize);
for(int i=0;i<threadeSize;i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
request();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown(); // 线程-1
}
}
});
thread.start();
}
countDownLatch.await();
long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+" 耗时 "+(endTime-startTime)+" ms"+" 请求了 "+ count+" 次");
}
}
结果
总结
- 比较和置换,这就是我们的CAS机制。 因为比较是原子性的,赋值也是原子性的。
扩展 (JAVA 中对CAS的实现 和 CAS的弊端)
java 中对CAS的支持
- java 提供了CAS操作的支持,具体在sum.misc.unsafe 类中
@ForceInline
public final boolean compareAndSwapObject(Object o, long offset,
Object expected,
Object x) {
return theInternalUnsafe.compareAndSetObject(o, offset, expected, x);
}
/**
* Atomically updates Java variable to {@code x} if it is currently
* holding {@code expected}.
*
* <p>This operation has memory semantics of a {@code volatile} read
* and write. Corresponds to C11 atomic_compare_exchange_strong.
*
* @return {@code true} if successful
*/
@ForceInline
public final boolean compareAndSwapInt(Object o, long offset,
int expected,
int x) {
return theInternalUnsafe.compareAndSetInt(o, offset, expected, x);
}
/**
* Atomically updates Java variable to {@code x} if it is currently
* holding {@code expected}.
*
* <p>This operation has memory semantics of a {@code volatile} read
* and write. Corresponds to C11 atomic_compare_exchange_strong.
*
* @return {@code true} if successful
*/
@ForceInline
public final boolean compareAndSwapLong(Object o, long offset,
long expected,
long x) {
return theInternalUnsafe.compareAndSetLong(o, offset, expected, x);
}
ABA 问题
什么是ABA问题
-
A线程获取到了期望值 也计算出了新值,但是时间片用完了,轮到B线程了,B线程也获的了期望值也计算出了新值,他将原来的count 改成了一个数N,紧接着又改回来了。这时轮到A线程去进行CAS操作,发现期望值和get到的值一直,就进行了 赋新值操作。根本没有发觉到count 的值改变过。
-
代码
import java.util.concurrent.atomic.AtomicInteger;
public class CasABADemo01 {
public static AtomicInteger a = new AtomicInteger();//Integer 的原子类
public static void main(String[] args){
Thread main = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("操作线程 "+ Thread.currentThread().getName()+" , 初始值 L: "+a.get());
try {
int expectNum = a.get();
int newNum = expectNum + 1;
Thread.sleep(1000);//睡一下 让别人先执行
boolean result = a.compareAndSet(expectNum, newNum);//成功返回true 失败返回 false
System.out.println("操作线程 "+ Thread.currentThread().getName()+" , CAS操作 "+result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"主线程");
Thread other = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(20);//确保main先执行
a.incrementAndGet();//a+1
System.out.println("操作线程 "+ Thread.currentThread().getName()+" ,increment 后的值: "+a.get());
a.decrementAndGet();//a-1
System.out.println("操作线程 "+ Thread.currentThread().getName()+" ,decrement 后的值: "+a.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"干扰线程");
main.start();
other.start();
}
}
- 结果
怎么解决
- 增加一个版本号
- 代码
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class CasABADemo02 {
public static AtomicStampedReference<Integer> a = new AtomicStampedReference(1,1);//Integer 的原子类
public static void main(String[] args){
Thread main = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("操作线程 "+ Thread.currentThread().getName()+" , 初始值 L: "+a.getReference());
try {
int expectReference = a.getReference();
int newReference = expectReference + 1;
int expectStamp = a.getStamp();
int newStamp = expectStamp +1;
Thread.sleep(1000);//睡一下 让别人先执行
boolean result = a.compareAndSet(expectReference, newReference,expectStamp,newStamp);//成功返回true 失败返回 false
System.out.println("操作线程 "+ Thread.currentThread().getName()+" , CAS操作 "+result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"主线程");
Thread other = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(200);//确保main先执行
a.compareAndSet(a.getReference(),a.getReference()+1,a.getStamp(),a.getStamp()+1);
System.out.println("操作线程 "+ Thread.currentThread().getName()+" ,increment 后的值: "+a.getReference());
a.compareAndSet(a.getReference(),a.getReference()-1,a.getStamp(),a.getStamp()+1);
System.out.println("操作线程 "+ Thread.currentThread().getName()+" ,decrement 后的值: "+a.getReference());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"干扰线程");
main.start();
other.start();
}
}
- 结果
*
本文参考 https://www.bilibili.com/video/av94403695?p=1 小刘老师 (疯狂推荐 )