简单了解原子操作(CAS)

首先,简单地了解下悲观锁和乐观锁的概念。

悲观锁

具有强烈的独占和排他特性。在有悲观锁的情况下,对数据进行处理,数据会处于锁定状态。前面讲到的synchronized同一时间只允许一个线程访问某块资源,其他线程处于阻塞状态,就是一个独占锁,是悲观锁中的一种。悲观锁适用于写操作比较多的场景。

乐观锁

对数据有更加宽松的加锁机制,允许多个线程同时访问对某块资源,一般通过版本号机制+CAS来实现。乐观锁适用于读操作比较多的场景。

什么是CAS?

CAS, Compare And Swap(或者Compare And Set),意思是比较并替换。CAS的操作过程包含三个运算符:内存地址V,期望的值A和新值B。如果V上存放的值等于期望的值A,则将地址上的值赋为新值B,如果不相等,则进行 CAS自旋。CAS自旋就是在一个循环里不断的做CAS操作,直到成功为止。图示如下:

 

CAS保证线程的安全:将CAS操作交给硬件(CPU和内存),利用CPU的能力,实现硬件层面的阻塞,再加上volatile变量的特性来实现基于原子操作的线程安全。

CAS存在的问题

ABA问题:内存地址V上的值为A,在CAS操作过程中,值A变成了值B又变成值A,内存地址V上旧值=期望值A校验通过(值相等),内存地址V上的值是相同的,但是实际上是已经变化了的,这就是ABA问题。ABA问题解决方法是加上版本号机制,即内存地址V上的值A每次变化都加上版本号,变化过程:A→B→A变成1A→2B→3A。

循环时间长开销大

写操作比较频繁(线程多),导致内存地址V上旧值频繁发生变化,显然会导致部分线程CAS修改值不成功,进而会CAS自旋操作次数增多,导致CPU的执行开销增大。这也是上面提到的乐观锁适用于读操作多的场景的原因。

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

因为每个内存地址上只存放一个对象,所以,只有一个共享变量时,可以通过CAS自旋的方式来保证原子操作,但是多个共享变量时,CAS自旋是无法保证操作的原子性的。这个时候,可以使用JDK提供了AtomicReference类,将多个变量放在一个对象中执行CAS操作,从而保证对象之间的原子性。

JDK中原子操作相关的类

1)、基本类型:AtomicBoolean,AtomicInteger,AtomicLong

使用(以AtomicInteger为例):

package com.su.mybatis.oracle.controller;

import java.util.concurrent.atomic.AtomicInteger;

public class Test {
    
    public static void main(String[] args) {
        AtomicInteger num = new  AtomicInteger(1);
        num.compareAndSet(1, 5);//比较交换值
        System.out.println(num.get());
        num.addAndGet(10);//相加再获取操作后的值
        System.out.println(num.get());
    }
}

输出结果:

5
15

compareAndSet方法(类似,之后的类型这块比较源码省略):

  /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

2)、数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

使用(以AtomicIntegerArray为例):

package com.su.mybatis.oracle.controller;

import java.util.concurrent.atomic.AtomicIntegerArray;

public class Test {
    
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        AtomicIntegerArray array = new AtomicIntegerArray(arr);
        System.out.println(array.get(2));//获取索引为2的值
        array.compareAndSet(2, 3, 30);//将索引为2的值改成30
        System.out.println(array.get(2));//获取索引为2的值
        array.addAndGet(2, 5);
        System.out.println(array.get(2));//获取索引为2的值
    }
}

输出结果:

3
30
35

3)、 对象:AtomicReference,AtomicMarkableReference,AtomicStampedReference

AtomicReference:

package com.su.mybatis.oracle.controller;

import java.util.concurrent.atomic.AtomicReference;

import com.alibaba.fastjson.JSON;

public class Test {
    
    public static void main(String[] args) {
        User user = new User("su", 25);
        AtomicReference<User> aUser = new AtomicReference<User>(user);
        System.out.println(JSON.toJSONString(aUser.get()));
        aUser.compareAndSet(user, new User("test", 20));//修改
        System.out.println(JSON.toJSONString(aUser.get()));
        
    }
}

class User {
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    private volatile String name;//保证变量的可见性
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

输出结果:

{"age":25,"name":"su"}
{"age":20,"name":"test"}

AtomicMarkableReference和AtomicStampedReference都是用来CAS中的ABA问题。两者存在区别:

AtomicMarkableReference强调是否修改,源码中存在一个boolean参数mark,用来标识指是否进行过修改;

 private static class Pair<T> {
        final T reference;
        final boolean mark;
        private Pair(T reference, boolean mark) {
            this.reference = reference;
            this.mark = mark;
        }
        static <T> Pair<T> of(T reference, boolean mark) {
            return new Pair<T>(reference, mark);
        }
    }

AtomicStampedReference强调修改次数(版本号),源码中存在一个int参数stamp,用来标识修改次数(版本号),有兴趣的朋友可以看下源码,这里不多说明。

 private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }

4)、原子更新字段:AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater

以AtomicIntegerFieldUpdater为例:

package com.su.mybatis.oracle.controller;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class Test {
    
    public static void main(String[] args) {
        AtomicIntegerFieldUpdater<User> aUser = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");//基于反射
        User user = new User(25);
        System.out.println("age:" + user.getAge());
        aUser.compareAndSet(user, 25, 20);
        System.out.println("age:" + user.getAge());
    }
}

class User{
    public User(int age){
        this.age = age;
    }
    public volatile int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

输出结果:

age:25
age:20

 

 

如果有写的不对的地方,请大家多多批评指正,非常感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值