本文基于jdk8
参考:
黑马程序员深入学习Java并发编程,JUC并发编程全套教程_哔哩哔哩_bilibili
CAS原理
CAS:比较和交换(设置) Compare And Swap(Set)。当A的值为5的时候,给A设置值为10。这里涉及到的比较和设置值的操作是原子的。
CAS的操作系统层实现
操作系统层面的CAS是一条CPU的原子指令(cmpxchg指令),正是由于该指令具备了原子性,因此使用CAS操作数据时不会造成数据不一致的问题。
CAS的Java层实现
Java是把底层C++代码调用的这个原子指令封装到native方法中了。不过还提供了Unsafe类用来操作相关线程等。但这个类无法直接调用,只能通过反射来使用。该类不建议使用。
获取Unsafe
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeAccessor {
static Unsafe unsafe;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new Error(e);
}
}
static Unsafe getUnsafe() {
return unsafe;
}
}
使用Unsafe
import sun.misc.Unsafe;
import java.lang.reflect.Field;
class Student {
volatile int id;
volatile String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
public static void main(String[] args) throws NoSuchFieldException {
Unsafe unsafe = UnsafeAccessor.getUnsafe();
Field id = Student.class.getDeclaredField("id");
Field name = Student.class.getDeclaredField("name");
// 获得成员变量的偏移量
long idOffset = UnsafeAccessor.unsafe.objectFieldOffset(id);
long nameOffset = UnsafeAccessor.unsafe.objectFieldOffset(name);
Student student = new Student();
// 使用 cas 方法替换成员变量的值
UnsafeAccessor.unsafe.compareAndSwapInt(student, idOffset, 0, 20); // 返回 true
UnsafeAccessor.unsafe.compareAndSwapObject(student, nameOffset, null, "张三"); // 返回 true
System.out.println(student);
}
}
CAS中的ABA问题
某个共享变量在线程1中刚获取到值为A,后面要对这个值进行CAS操作,把它变成C。
但是线程1在CAS之前,线程2也读到了共享变量的值,并且它先于线程1对改共享变量的CAS操作,把这个共享变量变成B,最后在线程1在CAS操作之前又把这个变量改成A。
当线程1要执行CAS操作时,它一看还是原来的A,就直接执行了,但实际上并不是原来的A了
import java.util.concurrent.atomic.AtomicReference;
import static java.lang.Thread.sleep;
public class CasAba {
static volatile AtomicReference<String> ref = new AtomicReference<>("A");
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程获取A");
// 获取值 A
// 这个共享变量被它线程修改过?
String prev = ref.get();
other();
sleep(1000);
// 尝试改为 C
if (ref