概述

参考博客
CAS:Compare and Set 比较与设置

使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。

多个线程对一个公共变量进行累加操作,如果使用 synchronized 来对累加操作进行加锁的话,未免显得太过重。因为 synchroinzed 其实会导致多个线程对累加操作进行串行化,其实会影响执行效率。

所以此时可以使用CAS来对公共变量进行累加操作。CAS其实是一种无锁化的并发机制。通过CAS机制可以保证多线程修改一个数值的安全性。

  1. public class CasTest {
  2. private AtomicInteger data = new AtomicInteger(0);
  3. // 多线程并发执行
  4. data.incrementAndGet();
  5. }

保证了并发编程的原子性(只能保证一个公共变量的原子性),可见性,有序性。

实现原理

比如有两个线程对同一个共享变量进行累加操作。使用cas机制的话,线程1先去主内存中获取共享变量的值至自己的本地内存里,然后尝试修改共享变量的值,比如【如果共享变量值为0,那我就设置为1】的情况,是正确的,那线程1就能够设置成功,并刷新新的值至主内存。
此时,如果另外一个线程同样也对共享变量做累加操作,使用cas机制,尝试做【如果共享变量值为0,那我就设置为1】的操作,就发现共享变量的值已经不是为0了,那本次的累加操作就会失败,线程2要重新进行cas操作,尝试做【如果共享变量值为1,那我就设置为2】 的操作,此时发现共享变量的值就是1,那这次的累加操作就会成功,线程2就把共享变量成功设置为2。

CAS可能会出现的问题

ABA问题

CAS需要检查操作值有没有发生改变,如果没有发生改变则更新。但是存在一种情况:如果一个值原来是A,变成了B,然后又变成了A,那么CAS检查的时候就会认为没有发生改变,但是实质上它已经发生了改变。这就是ABA问题。
解决方案:沿袭数据库中常用的乐观锁方式,添加一个版本号可以解决。原来的变化路径A->B->A就变成了1A->2B->3A。在java1.5后atomic包提供了AtomicStampedReference来解决ABA问题,解决思路就是这样的。

自旋时间过长

使用CAS时非阻塞同步,也就是不会将线程挂起,但是会自旋(死循环),比如一个线程尝试设值失败,再重新设置,又失败,反复循环。如果CAS自旋长时间不成功,则会给CPU带来非常大的开销。
再大量线程高并发更新AtomicInteger的时候,这种问题可能会比较明显,会导致大量线程空循环,自旋转,性能和效率都不是特别好。

Java8推出一个新的类,LongAdder,尝试使用分段CAS以及自动分段迁移的方式来大幅提升多线程高并发执行CAS操作的性能。

image.png
在LongAddr的底层实现中,首先会有一个base值,刚开始多线程来不停累加数值,都是对base进行累加,比如刚开始累加成了base=5。
接着如果发现并发更新的线程数量太多了,就会开始实行 分段CAS机制,内部搞一个cell数组,每个数组是一个数值的分段。这时,让大量的线程分别去对不同的cell内部的value值进行cas累加操作,这样就把cas计算压力分散到了不同的cell分段数值中了。
这种做法可以大幅度降低多线程并发更新同一个数值时出现无限循环的问题,大幅度提升了多线程并发更新数值的性能和效率。
同时LongAdder内部实现了自动分段迁移机制,也就是如果某个cell的value执行cas失败了,那么就会自动去找另外一个cell分段内的value值进行cas操作。
最后,累加的总值就是, base值加上所有cell分段数值的总和。

只能保证一个共享变量的原子操作

当对一个共享变量执行操作时cas能保证其原子性,如果对多个共享变量进行操作,cas不能保证其原子性。
解决方案:把多个变量整成一个变量。(1)利用对象整合多个共享变量,即一个类中的成员变量就是这几个共享变量,然后将这个对象做cas操作就可以保证其原子性。atomic中提供了AtomicReference来保证引用对象之间的原子性。

JUC下atomic包的原子操作类

基本数据类型的原子类

  1. AtomicInteger / AtomicLong / AtomicBoolean
  2. AtomicInteger 常用方法
  3. // 以原子方式将输入的数值value与原本的值相加,并返回最后结果
  4. addAndGet(int value);
  5. // 以原子的方式将实例的原值进行加1操作,并返回最后的结果
  6. incrementAndGet();
  7. // 以实例中的值更新为新值value,并返回旧值
  8. getAndSet(int value);
  9. // 以原子的方式将示例中的原值加1,返回的是自增前的旧值
  10. getAndIncrement();
  1. public class AtomicDemo {
  2. private static AtomicInteger atomicInteger = new AtomicInteger(1);
  3. public static void main(String[] args) {
  4. System.out.println(atomicInteger.getAndIncrement()); // 1
  5. System.out.println(atomicInteger.get()); // 2
  6. }
  7. }

数组类型的原子操作类

  1. AtomicIntegerArray / AtomicLongArray / AtomicReferenceArray(引用类型数组)
  2. AtomicIntegerArray 常用方法
  3. // 以原子更新的方式将数组中索引为i的元素与输入值value相加
  4. addAndGet(int i, int value)
  5. // 以原子更新的方式将数组索引为i的元素自增加1
  6. getAndIncrement(int i)
  7. // 将数组中索引为i的位置的元素进行更新
  8. compareAndSet(int i, int expect, int update)
  1. public class AtomicDemo {
  2. private static int[] value = new int[]{1, 2, 3};
  3. private static AtomicIntegerArray integerArray = new AtomicIntegerArray(value);
  4. public static void main(String[] args) {
  5. //对数组中索引为1的位置的元素加5
  6. int result = integerArray.getAndAdd(1, 5);
  7. System.out.println(integerArray.get(1));// 7
  8. System.out.println(result); // 2
  9. }
  10. }

引用类型的原子操作类

  1. AtomicReference
  2. public class AtomicDemo {
  3. private static AtomicReference<User> reference = new AtomicReference<>();
  4. public static void main(String[] args) {
  5. User user1 = new User("a", 1);
  6. reference.set(user1);
  7. User user2 = new User("b",2);
  8. User user = reference.getAndSet(user2);
  9. System.out.println(user); // User{userName='a', age=1}
  10. System.out.println(reference.get()); // User{userName='b', age=2}
  11. }
  12. static class User {
  13. private String userName;
  14. private int age;
  15. public User(String userName, int age) {
  16. this.userName = userName;
  17. this.age = age;
  18. }
  19. @Override
  20. public String toString() {
  21. return "User{" +
  22. "userName='" + userName + '\'' +
  23. ", age=" + age +
  24. '}';
  25. }
  26. }
  27. }

字段类型的原子操作类

  1. 如果需要更新对象的某个字段,并在多线程的情况下,能够保证线程安全,可以使用以下原子操作类
  2. AtomicIntegerFieldUpdater: 原子更新整型字段类
  3. AtomicLongFieldUpdater: 原子更新长整型字段类
  4. AtomicStampedReference:原子更新引用类型,这种更新方式会带有版本号,解决CASABA问题
  1. public class AtomicDemo {
  2. private static AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
  3. public static void main(String[] args) {
  4. User user = new User("a", 1);
  5. int oldValue = updater.getAndAdd(user, 5);
  6. System.out.println(oldValue); // 1
  7. System.out.println(updater.get(user)); // 6
  8. }
  9. static class User {
  10. private String userName;
  11. public volatile int age;
  12. public User(String userName, int age) {
  13. this.userName = userName;
  14. this.age = age;
  15. }
  16. @Override
  17. public String toString() {
  18. return "User{" +
  19. "userName='" + userName + '\'' +
  20. ", age=" + age +
  21. '}';
  22. }
  23. }
  24. }