目录
CAS 介绍:
CAS,Compare And Swap,即比较并交换。Doug lea 大神在同步组件中大量使用 CAS 技术鬼斧神工地实现了 Java 多线程的并发操作。整个 AQS 同步组件、Atomic 原子类操作等等都是以 CAS 实现的。可以说 CAS 是整个 J.U.C 的基石。
CAS 比较交换的过程 CAS(V,A,B):
V-一个内存地址存放的实际值、A-旧的预期值、B-即将更新的值,当且仅当预期值 A 和内存值 V 相同时,将内存值修改为 B 并返回 true,否则什么都不做,并返回 false。
这个我们一会在下面详细解释上述的交换过程
代码演示CAS:
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(1);
//mian do someting----
System.out.println(atomicInteger.compareAndSet(1,2)+
"\t 当前最新数据:"+atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(1,3)+
"\t 当前最新数据:"+atomicInteger.get());
}
}
compareAndSet源码:参数一expectedValue是预期的旧值(第一次从主物理内存中拷贝到工作内存中的旧值,即就是我们第一次拿到的值的拷贝),参数二newValue是修改成的新值
//U是Unsafe的一个实例
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
private static final long VALUE;// VALUE是内存地址的值
public final boolean compareAndSet(int expectedValue, int newValue) {
return U.compareAndSetInt(this, VALUE, expectedValue, newValue);//this是当前对象,即atomicInteger 实例
}
CAS底层:
例如我们这路选择原子整型类的一个方法getAndIncrement方法来实现类似于i++的原子操作:
package com.fan.applet;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
static int a = 0;//声明非原子类的整型变量,static证明只有一份,共享的资源
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(0);
for (int i = 1; i <= 10; i++) {
//创建十个线程
new Thread(()->{
for (int j = 0; j < 1000; j++) {
a++;
atomicInteger.getAndIncrement();//i++的原子操作方法
}
},String.valueOf(i)).start();
}
while(Thread.activeCount()>2){//gc和异常处理的线程在运行着
Thread.yield();//阻塞操作,main出让自己的cpu时间,等待以上十个线程计算完
}
System.out.println("非原子类计算结果:"+a);
System.out.println("原子类计算结果:"+atomicInteger.get());
}
}
非原子运算结果多线程不安全。
getAndIncrement方法的源码:类似于i++
Unsafe类介绍
Java中要想使用CAS原子的修改某值,怎么做呢?幸运的是Java提供了这样的API,就是在rt.jar下的sun.misc.Unsafe.java类中。Unsafe,中文名不安全的(因为能直接操作内存地址,所以不安全),也被称为魔术类,魔法类。
Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,一旦能够直接操作内存,这也就意味着
(1)不受JVM管理,意思就是使用Unsafe操作内存无法被JVM GC,需要我们手动GC,稍有不慎就会出现内存泄漏。
(2)Unsafe的不少方法中必须提供原始地址(内存地址)和被替换对象的地址,并且偏移量要自己计算(其提供的有计算偏移量的方法),所以一旦出现问题就是JVM崩溃级别的异常,会导致整个JVM实例崩溃,表现为应用程序直接crash掉。
(3)直接操作内存,所以速度更快,在高并发的条件之下能够很好地提高效率。
因此,从上面三个角度来看,虽然在一定程度上提升了效率但是也带来了指针的不安全性。这也是它被取名为Unsafe的原因吧。
下面我们深入到源码中看看,提供了什么方法直接操作内存。
打开Unsafe这个类,我们会发现里面有大量的被native关键字修饰的方法,这意味着这些方法是C语言提供的实现,底层调的是C语言的库函数,我们无法直接看到他的源码实现,需要去从OpenJDK去看了。另外还有一些基于native方法封装的其他方法,整个Unsafe中的方法大致可以归结为以下几类:
(1)初始化操作
(2)操作对象属性
(3)操作数组元素
(4)线程挂起和恢复
(5)CAS机制
CAS底层原理:
getAndIncrement()(加一的方法)–>调用getAndAddInt(Object o, long offset, int delta)–>调用weakCompareAndSetInt(o, offset, v, v + delta)
//CAS本质
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
重点理解下面这张图的getAndAddInt方法:
/*
Object o :AtomicInteger对象实例本身
long offset:AtomicInteger对象的值的引用内存地址
int delta:需要变动的数量
int v: 是用Object o, long offset 找出的主内存中真实的值
过程:‘
1.用该对象的第一次do操作读取内存中的数据的值到自己的工作内存中的值,作为预期的旧值。
2.然后经过一些操作之后,再次看内存中的最新值v(可见性)和预期的旧值(拷贝到自己工作内存中的旧值)是否相等,如果相等,则设置成新值v + delta,不相等,
则继续循环读取内存最新的值,再次比较。直到更新成功为止。
*/
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
getAndAddInt方法:
Unsafe类的compareAndSwapInt方法,它有四个参数,分别是要修改的类实例对象、要修改的值的偏移量、旧值、新值。解释一下偏移量
假设有两个线程想要更新变量 V=10;
线程A 想把V 更新为20;
线程B 想把V 更新为30;
对于线程A 来说,V 值为10,旧的预期值E当然也为10,希望更新的值N 为20;
如果B 线程在A 线程更新之前完成对V 的更新,那么此时V 的值为30 。
那么对于A 线程来说 V 的值等于30 ,而预期值E 还是10,这说明V 的值已经被更新了,A线程就什么也不做。
CAS的缺点:
- CPU开销大,当多个线程同时尝试修改某一变量时,线程将尝试不断自旋,直至修改成功,这将给CPU带来压力。
- 只能保证单个变量的原子性操作,对于多个变量进行的原子性更新,CAS无能为力。
- ABA问题
ABA 问题:
CAS 需要检查操作值有没有发生改变,如果没有发生改变则更新。但是存在这样一种情况:如果一个值原来是 A,变成了 B,然后又变成了 A,那么在 CAS 检查的时候会认为没有改变,但是实质上它已经发生了改变,这就是 ABA 问题。
解决方案可以沿袭数据库中常用的乐观锁方式,添加一个版本号可以解决。原来的变化路径 A->B->A 就变成了 1A->2B->3A。
在 java 1.5 后的 atomic 包中提供了 AtomicStampedReference 来解决 ABA 问题,解决思路就是这样的。
原子引用类AtomicReference:
package com.fan.applet;
import java.util.concurrent.atomic.AtomicReference;
class User{
String userName;
int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
public class ABATest {
public static void main(String[] args) {
User z3 = new User("张三",11);
User li4 = new User("李四",22);
//原子引用类
AtomicReference<User> userAtomicReference = new AtomicReference<>();
userAtomicReference.set(z3);//将对象设置成原子类对象
System.out.println(userAtomicReference.compareAndSet(z3,li4)+
"\t"+userAtomicReference.get().toString());
System.out.println(userAtomicReference.compareAndSet(z3,li4)+
"\t"+userAtomicReference.get().toString());
}
}
演示ABA问题:
package com.fan.applet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class ABATest2 {
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
public static void main(String[] args) {
new Thread(()->{
atomicReference.compareAndSet(100,101);//A-->B
atomicReference.compareAndSet(101,100);//B-->A
},"t1").start();
new Thread(()->{
//暂停一秒,保证上面的线程进行了一次ABA操作
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100,2022)+
"\t"+atomicReference.get());
},"t2").start();
}
}
代码演示解决ABA问题:
带版本号的原子引用类AtomicStampedReference解决ABA问题:
package com.fan.applet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABATest3 {
//给定初始值和版本号
static AtomicStampedReference<Integer> atomicStampedReference =
new AtomicStampedReference(100,1);
public static void main(String[] args) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+
"\t第一次的版本号:"+atomicStampedReference.getStamp());
//进行ABA操作
atomicStampedReference.compareAndSet(100,101,
atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+
"\t第二次的版本号:"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,
atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+
"\t第三次的版本号:"+atomicStampedReference.getStamp());
},"t3").start();
new Thread(()->{
//先获取版本号,然后休眠2秒,让t3先执行
int oldStamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+
"\t第一次的版本号:"+oldStamp);
//先让上面线程进行一次ABA的操作
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = atomicStampedReference.compareAndSet(100, 2020,
oldStamp, atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName()+"\t修改成功否:"+b+
",当前最新版本号:"+atomicStampedReference.getStamp()+
",当前实际最新值:"+atomicStampedReference.getReference());
},"t4").start();
}
}