Java并发编程:CAS

1. CAS算法

  • CAS 全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。CAS是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。java.util.concurrent.atomic包中的原子类就是通过CAS来实现了乐观锁。
  • CAS算法涉及到三个操作数:
    • 需要读写的内存值 V
    • 进行比较的值 A(A本质上指的是“旧值”)
    • 要写入的新值 B
  • 当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。一般情况下,“更新”是一个不断重试的操作。
  • 有没有可能在判断了 V == A 之后,正准备更新它的新值的时候,被其它线程更改了 V 的值呢?不会的,因为CAS是一种原子操作,它是一种系统原语,是一条CPU的原子指令,从CPU层面保证它的原子性。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。

Java 实现 CAS 的原理:Unsafe

  • 在 Java 中,如果一个方法是 native 的,那 Java 就不负责具体实现它,而是交给底层的 JVM 使用 C 或者 C++ 去实现。在 Java 中,有一个 Unsafe 类,它在 sun.misc 包中,它里面是一些 native 方法,其中就有几个关于 CAS 的方法。Unsafe 类中对 CAS 的实现是 C++ 写的,它的具体实现和操作系统、CPU 都有关系。

CAS 实现原子操作的三大问题:

  • ABA问题。CAS 需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是 A,后来变成了 B,然后又变成了 A,那么 CAS 进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA 问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成了“1A-2B-3A”。从 JDK 1.5 开始,JDK 的 atomic 包里提供了一个类 AtomicStampedReference 类来解决 ABA 问题,具体操作封装在 compareAndSet() 中。compareAndSet() 首先检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值。
  • 循环时间长开销大。CAS 多与自旋结合。自旋 CAS 操作如果长时间不成功,会导致其一直自旋,给 CPU 带来非常大的开销。解决思路是让 JVM 支持处理器提供的 pause 指令,pause 指令能让自旋失败时 cpu 睡眠一小段时间再继续自旋。
  • 只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS 能够保证原子操作,但是对多个共享变量执行操作时,CAS 是无法保证操作的原子性的。JDK 从 1.5 开始提供了 AtomicReference 类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行 CAS 操作。或者使用锁来解决这一问题,因为锁内的临界区代码可以保证只有当前线程能操作。

2. 线程安全的原子操作类

  • java.util.concurrent.atomic(Java并发原子包)包里的类基本都是使用 Unsafe 实现的包装类。

2.1 原子更新基本类型

  • AtomicInteger:原子更新整型
  • AtomicLong:原子更新长整型
  • AtomicBoolean:原子更新布尔类型
package com.java.atomic;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest {
    //AtomicInteger类中声明了value属性:private volatile int value;
    //new AtomicInteger(10); 等价于 value = 10;
    static AtomicInteger ai = new AtomicInteger(10);

    public static void main(String[] args) {

        new Thread(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(100); //该线程先睡100毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("incrementAndGet : " + ai.incrementAndGet());
        }, "A").start();


        new Thread(() -> {
            System.out.println("get : " + ai.get());
            System.out.println("getAndIncrement : " + ai.getAndIncrement());
        }, "B").start();
    }
    /**
     * 执行结果:
     * get : 10
     * getAndIncrement : 10
     * incrementAndGet : 12
     */
}

getAndIncrement()方法实现原子操作的原理:

  • 第一步先取出AtomicInteger里存储的数值;
  • 第二步对取出来的数值进行+1操作;
  • 第三步调用compareAndSet方法进行原子更新操作,该方法先检查当前AtomicInteger里存储的数值是否等于current,等于意味着AtomicInteger里存储的数值没有被其他线程修改过,此时将AtomicInteger里存储的数值更新成next;如果不等,compareAndSet会返回false,程序会再次进入for循环重新进行compareAndSet操作。
	public final int getAndIncrement() {
        for(;;) {
        	int current = get();
        	int next = current + 1;
        	if(compareAndSet(current,next)) {
        		return current;
        	}
        }
    }
    
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

Unsafe只提供了3种CAS方法,查看AtomicBoolean源码可以看到它是先把Boolean转换成整型,再使用compareAndSwapInt进行CAS,所以原子更新char、float、double变量也可使用类似的思路。

//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);

2.2 原子更新数组

  • AtomicIntegerArray:原子更新整型数组里的元素
  • AtomicLongArray:原子更新长整型数组里的元素
  • AtomicReferenceArray:原子更新引用类型数组里的元素
package com.java.atomic;

import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicIntegerArrayTest {
    static int[] array = new int[]{1, 2, 3};
    //数组array通过构造器传入,然后AtomicIntegerArray会将当前数组复制一份,
    //所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响传入的数组。
    static AtomicIntegerArray aia = new AtomicIntegerArray(array);

    public static void main(String[] args) {
        System.out.println("索引1处的元素 = " + aia.get(1));
        System.out.println("索引1处的元素 = " + aia.getAndSet(1, 10));
        System.out.println("索引1处的元素 = " + aia.get(1));
        System.out.println("array[1] = " + array[1]);
    }
    /**
     * 执行结果:
     * 索引1处的元素 = 2
     * 索引1处的元素 = 2
     * 索引1处的元素 = 10
     * array[1] = 2
     */
}

2.3 原子更新引用

原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类。

  • AtomicReference:原子更新引用类型
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段
  • AtomicMarkableReference:原子更新带有标记位的引用类型(可以原子更新一个布尔类型的标记位和引用类型)
package com.java.atomic;

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceTest {
    static AtomicReference<User> ar = new AtomicReference<User>();

    public static void main(String[] args) {
        User user = new User("AA", 18);
        ar.set(user);
        User updateUser = new User("BB", 81);
        boolean flag = ar.compareAndSet(user, updateUser);
        System.out.println(flag); //true
        System.out.println(ar.get().getName()); //BB
        System.out.println(ar.get().getAge()); //81
    }

    static class User {
        private String name;
        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }
    }
}

2.4 原子更新属性 (字段)

如果需要原子的更新某个类里的某个字段时,需要使用原子更新字段类。

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
  • AtomicLongFieldUpdater:原子更新长整型的字段的更新器
  • AtomicStampedReference:原子更新带有版本号的引用类型(该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题)
package com.java.atomic;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterTest {
    //创建原子更新器,并设置需要更新的对象所属的类和对象的属性
    //public abstract class AtomicIntegerFieldUpdater<T> { //...... }
    //AtomicIntegerFieldUpdater是抽象类,需要使用其静态方法newUpdater创建一个实现类对象
    static AtomicIntegerFieldUpdater<User> aifu = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");

    public static void main(String[] args) {
        User user = new User("XX", 24);
        System.out.println(aifu.getAndIncrement(user)); //24
        System.out.println(aifu.get(user)); //25
    }

    static class User {
        private String name;
        //被更新的字段必须使用 public volatile 修饰
        public volatile int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值