CAS(Compare And Set)

Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。

 

悲观锁与乐观锁

悲观锁,认为数据发生并发冲突的概率很大,所以读操作之前就上锁。synchronized关键字,ReentrantLock 都是悲观锁的典型例子。

对于乐观锁,认为数据发生并发冲突的概率比较小,所以读操作之前不上锁。等到写操作的时候,再判断数据在此期间是否被其 他线程修改了。如果被其他线程修改了,就把数据重新读出来,重复该过程;如果没有被修改,就写回去。判断数据是否被修改,同时写 回新值,这两个操作要合成一个原子操作,也就是CAS(Compare And Set)。

 

原子更新基本类型类

使用原子的方式更新基本类型,Atomic包提供了以下3个类。

❑ AtomicBoolean:原子更新布尔类型。

❑ AtomicInteger:原子更新整型。

❑ AtomicLong:原子更新长整型。

以上3个类提供的方法类似以AtomicInteger为例进行讲解,AtomicInteger的常用方法如下。

➢ int addAndGet(int delta):以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。

➢ boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。

➢ int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值。

➢ void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

➢ int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。

1
2
3
4
5
6
7
public class AtomicIntegerTest {
    static AtomicInteger ai = new AtomicInteger(1);
    public static void main(String[] args) {
        System.out.println(ai.getAndIncrement());
        System.out.println(ai.get());
    }
}

  

Unsafe的CAS

CAS函数,其实是封装的Unsafe类中的一个native函 数,如下所示。

1
2
3
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

AtomicInteger封装过的compareAndSet有两个参数。

第一个参数e xpect是指变量的旧值(是读出来的值,写回去的时候,希望没有被其 他线程修改,所以称为expect);

第二个参数update是指变量的新值 (修改过的,希望写入的值)。

当expect等于变量当前的值时,说明 在修改的期间,没有其他线程对此变量进行过修改,所以可以成功写 入,变量被更新为update,返回true;否则返回false。

Unsafe类是整个Concurrent包的基础,里面所有函数都是native 的。具体到compareAndSwapInt函数,如下所示。

1
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

该函数有4个参数。在前两个参数中,第一个是对象(也就是 AtomicInteger 对象),第二个是对象的成员变量(也就是AtomicInteger里面包的int变量value),后两个参数保持不变。 要特别说明一下第二个参数,它是一个long型的整数,经常被称为xxxOffset,意思是某个成员变量在对应的类中的内存偏移量(该变 量在内存中的位置),表示该成员变量本身。在Unsafe中专门有一个函数,把成员变量转化成偏移量,如下所示。

1
public native long objectFieldOffset(Field var1);

所有调用CAS的地方,都会先通过这个函数把成员变量转换成一个 Offset。以AtomicInteger为例:

从上面代码可以看到,无论是Unsafe还是valueOffset,都是静态 的,也就是类级别的,所有对象共用的。 在转化的时候,先通过反射(getDeclaredField)获取value成员 变量对应的Field对象,再通过objectFieldOffset函数转化成valueOffset。此处的valueOffset就代表了value变量本身,后面执行CAS操作 的时候,不是直接操作value,而是操作valueOffset。

 

原子更新数组 

通过原子的方式更新数组里的某个元素,Atomic包提供了以下3个类。

❑ AtomicIntegerArray:原子更新整型数组里的元素。

❑ AtomicLongArray:原子更新长整型数组里的元素。

❑ AtomicReferenceArray:原子更新引用类型数组里的元素。

注意,这里并不是说对整个数组的操作是原子的,而是针对数组中一个元素的原子操作而言。

AtomicIntegerArray类常用方法如下。

➢int addAndGet(int i,int delta):以原子方式将输入值与数组中索引i的元素相加。

➢boolean compareAndSet(int i,int expect,int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。

1
2
3
4
5
6
7
8
9
public class AtomicIntegerArrayTest {
    static int[] value = new int[]{1, 2};
    static AtomicIntegerArray ai = new AtomicIntegerArray(value);
    public static void main(String[] args) {
        ai.getAndSet(0, 3);
        System.out.println(ai.get(0));
        System.out.println(value[0]);
    }
}

需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响传入的数组。

 

原子更新引用类型

原子更新引用类型提供的类。Atomic包提供了以下3个类。

❑ AtomicReference:原子更新引用类型。

❑ AtomicReferenceFieldUpdater:原子更新引用类型里的字段。

❑ AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)。

以上几个类方法类似,以AtomicReference为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class AtomicReferenceTest {
    public static AtomicReference<User> atomicUserRef = new AtomicReference<User>();
    public static void main(String[] args) {
        User user = new User("conan", 15);
        atomicUserRef.set(user);
        User updateUser = new User("Shinichi", 17);
        atomicUserRef.compareAndSet(user, updateUser);
        System.out.println(atomicUserRef.get().getName());
        System.out.println(atomicUserRef.get().getOld());
    }
    public static class User {
        private String name;
        private int    old;
        public User(String name, int old) {
            this.name = name;
            this.old = old;
        }
        public String getName() {
            return name;
        }
        public int getOld() {
            return old;
        }
    }
}

 

原子更新字段类

原子更新字段类,Atomic包提供了以下3个类进行原子字段更新。

❑ AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。

❑ AtomicLongFieldUpdater:原子更新长整型字段的更新器。

❑ AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。

要想原子地更新字段类需要两步。

第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。

第二步,更新类的字段(属性)必须使用public volatile修饰符。

以上3个类提供的方法类似,以AstomicIntegerFieldUpdater为例,AstomicIntegerFieldUpdater的示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class AtomicIntegerFieldUpdaterTest {
    private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "old");
    public static void main(String[] args) {
        User conan = new User("conan", 10);
        System.out.println(a.getAndIncrement(conan));
        System.out.println(a.get(conan));
    }
    public static class User {
        private String name;
        public volatile int old;
        public User(String name, int old) {
            this.name = name;
            this.old = old;
        }
        public String getName() {
            return name;
        }
        public int getOld() {
            return old;
        }
    }
}

  

 

 

参考: Java并发编程的艺术 7.1 原子更新基本类型类

  Java并发实现原理:Atomic类

 

posted @   草木物语  阅读(845)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2018-01-25 js判断一个字符串是以某个字符串开头
2018-01-25 如何 通过 userAgent 区别微信小程序
点击右上角即可分享
微信分享提示