CAS提出
Demo01:
public class Demo01 {
//总访问量
static int count=0;
/**
* Q:耗时太长的原因是什么呢?
* A:程序中的request方法使用synchronize关键字修饰,保证在并发情况下,request方法同一时刻
* 只允许一个线程进入,request加锁相当于串行执行了,count的结果和我们预期的一致,只是耗时太长了
*
* Q:如何解决耗时长的问题?
* A:count++ 操作实际上由3步来完成!(jvm执行引擎)
* 1.获取count的值:记作A:A=count;
* 2.将A值+1,得到B:A+1
* 3.将B值赋值给count
*
* 升级第三步:
* 1.获取锁
* 2.获取以下count最新的值,记作LV
* 3.判断LV是否等于A,如果相等,则将B的值赋值给count,并返回true,否则返回false
* 4.释放锁
*
*/
//模拟访问的方法
private static void request() throws InterruptedException {
//模拟耗时5毫秒
TimeUnit.MILLISECONDS.sleep(5);
/**
* Q:分析一下问题出现在哪?
* A:count++ 操作实际上由3步来完成!(jvm执行引擎)
* 1.获取count的值:记作A:A=count;
* 2.将A值+1,得到B:A+1
* 3.将B值赋值给count
* 如果A,B两个线程同时执行count++,他们通知执行到上面步骤的第一步,得到的
* count是一样的,3步操作结束后,count只加1,导致count结果不正确!
* Q:怎么解决这个问题?
* A:对count++操作的时候,我们让多个线程排队处理,多个线程同时到达request()方法的时候,
* 只能允许一个线程可以进去操作,其他的线程在外面等待,等里面的处理完毕出来之后,
* 外面等着的在进去一个,这样操作的count++就是排队进行的,结果一定是正确的
*
* Q:怎么实现排队效果?
* A:java中synchronize关键字和ReentrantLock都可以实现对资源枷锁,保证并发正确性。
* 多线程的情况下可以保证被锁住的资源被“串行”访问
*
* 不用lock 可以用 原子类:AtomicInteger
*
*/
count++;
}
public static void main(String[] args) throws InterruptedException {
//开始时间
long startTime=System.currentTimeMillis();
int threadSize=100;
CountDownLatch countDownLatch = new CountDownLatch(threadSize);
for (int i = 0; i < threadSize; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//模拟用户行为。每个用户访问10次网站
try{
for (int j = 0; j < 10; j++) {
request();
}
}catch (Exception e){
e.printStackTrace();
}finally {
countDownLatch.countDown();
}
}
});
thread.start();
}
//怎么保证100个线程,结束之后,再执行后面代码
countDownLatch.await();
long endTime=System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+",耗时:"+(endTime-startTime));
System.out.println("count = "+count);
}
}
Demo02:在static 方法中加synchronize,(但花费的时间更多)
public class Demo02 {
//总访问量
static int count=0;
private static synchronized void request() throws InterruptedException {
//模拟耗时5毫秒
TimeUnit.MILLISECONDS.sleep(5);
count++;
}
}
Demo03:(CAS)
//CAS
public class Demo03 {
//总访问量
//回去主存中找
volatile static int count=0;
//模拟访问的方法
private static void request() throws InterruptedException {
//模拟耗时5毫秒
TimeUnit.MILLISECONDS.sleep(5);
int expectCount; //表示期望值
while(!compareAndSwap((expectCount=getCount()),expectCount+1)){
}
}
/**
* expectCount 期望值count
* newCount 需要给count赋值的新值
* 成功返回true 失败返回false
*/
private static synchronized boolean compareAndSwap(int expectCount,int newCount) {
//判断count当前值是否和期望值expectCount一致,如果一致 将newCount赋值给count
if(getCount()==expectCount){
count=newCount;
return true;
}
return false;
}
private static int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
//开始时间
long startTime=System.currentTimeMillis();
int threadSize=100;
CountDownLatch countDownLatch = new CountDownLatch(threadSize);
for (int i = 0; i < threadSize; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//模拟用户行为。每个用户访问10次网站
try{
for (int j = 0; j < 10; j++) {
request();
}
}catch (Exception e){
e.printStackTrace();
}finally {
countDownLatch.countDown();
}
}
});
thread.start();
}
countDownLatch.await();
long endTime=System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+",耗时:"+(endTime-startTime));
System.out.println("count = "+count);
}
}
CAS全称“CompareAndSwap”,翻译为“比较并替换”
定义:CAS操作包含三个操作数–内存位置(V),期望值(A)和新值(B)。如果内存位置的值与期望值匹配,那么处理器会自动将该位置更新为新值。否则,处理器不作任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。(CAS在一些特殊情况下仅返回是否成功,而不提取当前值)CAS有效的说明了“我认为位置V应该包含值A:如果包含该值,则将B放到这个位置:否则,不要更改该位置的值,只告诉我这个位置现在的值即可”
Q:怎么使用jdk提供的CAS支持?
A:java提供的CAS操作的支持在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:表示需要修改为的新值
Q:CAS实现原理是什么?
A:CAS通过调用JNI的代码实现,JNI:java native interface,允许java调用其他语言。而compareAndSwapxxx系列的方法就是借助“C语言”来调用cpu底层指令实现的。
以常用的Intel x86平台来说,最终映射到cpu的指令为“cmpxchg”,这是一个原子指令,cpu执行此指令时,实现比较并替换的操作。
Q:现代计算机有的上百核心,cmpxchg怎么保证多核心下的线程安全?
A:系统底层进行CAS操作的时候,会判断当前系统是否为多核心,如果是就给“总线”加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行CAS操作,也就是说CAS的原子性是平台级别的!
Q:什么是ABA问题?
A:CAS需要在操作值的时候检查下值有没有发送变化,如果没有发生变化则更新,但是如果一个值原来是A,在CAS方法执行之前,被其他线程修改为了B。然后又修改回了A,那么CAS方法执行检查的时候会发现它的值没有发生变化,但是实际上却变化了。这就是CAS的ABA问题。
Q:如何解决ABA问题?
A:解决ABA最简单的方案就是给值加上一个修改版本号每次值变化,都会修改它的版本号
CAS操作时都会对比此版本号
java中ABA问题解决方法(AtomicStampedReference)
AtomicStampedReference主要包含一个对象引用及一个可以自动更新的整数“stamp”的pair对象来解决ABA问题。
CAS中的ABA问题:(问题提出)
//CAS ABA问题
public class CasABADemo {
private static AtomicInteger a=new AtomicInteger(1);
public static void main(String[] args) {
Thread main=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("操作线程 "+Thread.currentThread().getName()+",初始值:"+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()+",CAS操作:"+isCASSuccess);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"主线程");
Thread other=new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(20);//让主线程先执行
a.incrementAndGet();//a + 1,a=2
System.out.println("操作线程 "+Thread.currentThread().getName()+",【increment】,值 = "+a.get());
a.decrementAndGet();//a - 1,a=1
System.out.println("操作线程 "+Thread.currentThread().getName()+",【decrement】,值 = "+a.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"干扰线程");
main.start();
other.start();
}
}
ABA问题解决:AtomicStampedReference
带版本号stamp
/**
* @param expectedReference 期望引用
* @param newReference 新值引用
* @param expectedStamp 期望引用的版本号
* @param newStamp 期望版本号
* @return {@code true} if successful
*/
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
//CAS ABA问题
public class CasABADemo02 {
private static AtomicStampedReference<Integer> a=new AtomicStampedReference(new Integer(10),1);
public static void main(String[] args) {
Thread main=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("操作线程 "+Thread.currentThread().getName()+",初始值:"+a.getReference());
try {
int expectReference=a.getReference();
int 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()+",CAS操作:"+isCASSuccess);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"主线程");
Thread other=new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(20);//让主线程先执行
a.compareAndSet(a.getReference(),a.getReference()+1,a.getStamp(),a.getStamp()+1);//a + 1,a=2
System.out.println("操作线程 "+Thread.currentThread().getName()+",【increment】,值 = "+a.getReference());
a.compareAndSet(a.getReference(),a.getReference()-1,a.getStamp(),a.getStamp()+1);//a 1 1,a=1
System.out.println("操作线程 "+Thread.currentThread().getName()+",【decrement】,值 = "+a.getReference());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"干扰线程");
main.start();
other.start();
}
}