1 概述

CAS即 Compare and Swap ,其是 JDK 提供的非阻塞原子性操作,它通过硬件保证了比较-更新操作的原子性。
或许我们可能会有这样的疑问,假设存在多个线程执行CAS操作并且CAS的步骤很多,有没有可能在判断V和E相同后,正要赋值时,切换了线程,更改了值。造成了数据不一致呢?答案是否定的,因为CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。

2 Unsafe

JDK 的此jar 包中 Unsafe 类提供了 别的原子性操作 Unsafe 类中的方法都是native 方法,它们使用JNI的方式访问本地C++实现库。

  1. //比较对象 obj 中偏移量 offset 的变量的值是否与 expect 相等 若相等 ,使用 update更新 然后返回 true,否则返回 false
  2. public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
  3. public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
  4. public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

3 ABA问题

关于 CAS 操作有 典的 ABA 具体如下:假如线程 使用CAS 修改初始值为A的变量X,那么线程1会首先去获取当前变量X的值(为A然后使用 CAS操作尝试修改X值为B,如果使 CAS操作成功了,那么程序运行一定是正确的吗?其实未必,这是因为有可能在线程 获取变 的值 后,在执行 AS 前,线程2使用 CAS 修改了变量X,然后又使用CAS 修改X变量的值为A,所以虽然线程1执行CAS时X的值是A, 但是这时己经不是线程1获取时的A了,这就是 ABA的问题。
ABA 产生是因为变量的状态值产生了环形转换,就是变量的值可以从A到B, 然后再从B到A。如果变量的值只能朝着一个方向转换,比入A到B,B到C,不构成环形,就不会存在问题。 JDK AtomicStampedReference 类给每个变量的状态值都配备了一个时间戳,避免了 ABA 问题 产生。

3.1 ABA问题示例

  1. public class ABATest {
  2. public static void main(String[] args) {
  3. Person p1 = new Person(1, "小明", 1);
  4. Person p2 = new Person(2, "小红", 2);
  5. Person p3 = new Person(3, "小李", 3);
  6. AtomicReference ar = new AtomicReference(p1);
  7. new Thread(() -> {
  8. boolean b = ar.compareAndSet(p1, p2);
  9. if (b) {
  10. p1.setName("小王");
  11. System.out.println("编号1姓名被修改为小王");
  12. }
  13. boolean b1 = ar.compareAndSet(p2, p1);
  14. System.out.println("b1:" + b1);
  15. }).start();
  16. new Thread(() -> {
  17. try {
  18. Thread.sleep(1000);
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. boolean b = ar.compareAndSet(p1, p3);
  23. if (b) {
  24. System.out.println("CAS 成功,"+p1.toString());
  25. }
  26. }).start();
  27. }
  28. }
  29. class Person {
  30. private Integer id;
  31. private String name;
  32. private Integer number;
  33. public Person(Integer id, String name, Integer number) {
  34. this.id = id;
  35. this.name = name;
  36. this.number = number;
  37. }
  38. public Integer getId() {
  39. return id;
  40. }
  41. public void setId(Integer id) {
  42. this.id = id;
  43. }
  44. public String getName() {
  45. return name;
  46. }
  47. public void setName(String name) {
  48. this.name = name;
  49. }
  50. public Integer getNumber() {
  51. return number;
  52. }
  53. public void setNumber(Integer number) {
  54. this.number = number;
  55. }
  56. @Override
  57. public String toString() {
  58. return "Person{" +
  59. "id=" + id +
  60. ", name='" + name + '\'' +
  61. ", number=" + number +
  62. '}';
  63. }
  64. }

输出:

  1. 编号1姓名被修改为小王
  2. b1:true
  3. CAS 成功,Person{id=1, name='小王', number=1}

3.2 解决ABA问题示例

  1. public class ResolveABATest {
  2. public static void main(String[] args) {
  3. Person p1 = new Person(1, "小明", 1);
  4. Person p2 = new Person(2, "小红", 2);
  5. Person p3 = new Person(3, "小李", 3);
  6. AtomicStampedReference asr = new AtomicStampedReference(p1, 1);
  7. int stamp = asr.getStamp();
  8. new Thread(() -> {
  9. boolean b = asr.compareAndSet(p1, p2, stamp, stamp + 1);
  10. if (b) {
  11. p1.setName("小王");
  12. }
  13. boolean b1 = asr.compareAndSet(p2, p1, asr.getStamp(), asr.getStamp() + 1);
  14. System.out.println("b1:" + b1);
  15. }).start();
  16. new Thread(() -> {
  17. boolean b = asr.compareAndSet(p1, p3, stamp, stamp + 1);
  18. if (b) {
  19. System.out.println("CAS 成功," + p1.toString());
  20. } else {
  21. System.out.println("CAS 失败!");
  22. }
  23. }).start();
  24. }
  25. }

输出:

  1. b1:true
  2. CAS 失败!