Java并发编程---原子操作CAS

实现并发操作的方法有两种:一种是使用锁(Synchronized和Lock),另外一种是使用原子操作(CAS)

Synchronized基于阻塞的锁机制可能会带来的问题:

a. 被阻塞的线程优先级很高
b.拿到锁的线程一直不释放锁怎么办?
c.大量的竞争消耗cpu,同时带来死锁或者其他安全问题

基于上述问题,提出了CAS原子操作
a.CAS原理:利用现代处理器都支持的CAS指令,循环这个指令,直到成功为止
b.CAS(Compare And Swap) : 指令级别上保证这是一个原子操作
c.与CAS相关的三个运算符:一个内存地址V,一个期望值A,一个新值B
基本思路:如果地址V上的值和期望的值A相等,就给地址V赋新值B,如果不是,不做任何操作。
(在(死循环)循环中不断进行CAS操作)

d.CAS也会带来一定的问题:

1)ABA问题(A–>B–>A):当第一次取内存地址V时,取到了A的值,可当它要执行CAS操作之前,已经有一个线程对A的值改为B后,再 改为A,这时候的值虽然一样,不过内容已经有过更改。(举例:在现实生活中,你放在座子上的以一杯水被同事喝完,又重新倒满一杯放在那里。很明显,它虽然还是那杯满着的水,可性质上却完全不一样了。) 解决方法:可以通过版本号解决 A1—>B2---->A3 (类似于乐观锁,大多是基于数据版本( Version )记录机制实现。)

2)开销问题:CAS操作长期不成功,cpu不断循环。

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

原子操作类的使用:
JDK中相关原子操作类的使用:
1)更新基本类型:AtomicBoolean, AtomicInteger, AtomicLong
简单的代码示例(AtomicInteger)

相关的方法也比较简单:getAndIncrement() 先取值再加1
incrementAndGet() 先加1再取值

package 原子操作;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author lenovo
 */
public class UseAtomicInt {

    static AtomicInteger ai=new AtomicInteger(10);

    public static void main(String[] args) {
        //先取值再加1
        System.out.println(ai.getAndIncrement());
        //先加1再取值
        System.out.println(ai.incrementAndGet());
        System.out.println(ai.get());
    }
}

结果展示:
在这里插入图片描述
2) 更新数组类:AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray
简单代码示例(AtomicIntegerArray)
在进行值的更改时,其实是进行了一个数组的拷贝,并重新赋值,所以原数组的值并没有改变。

package 原子操作;

import java.util.concurrent.atomic.AtomicIntegerArray;

/**
 * @author lenovo
 */
public class AtomicArray {

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

    }
}

结果展示(第一个结果表示改后新数组的值,第二个结果表示的是原数组的值)
在这里插入图片描述
3) 更新引用类型:AtomicReference, AtomicMarkableReference, AtomicStampedReference
引用类型的原子操作类可以封装多个数据,已达到多个变量的原子操作
简单代码示例 (AtomicReference) :
UserInfo这个类封装了姓名和年龄这两个变量,进行值改变的时候,也是重新进行拷贝,再进行值的改变。

package 原子操作;

import java.util.concurrent.atomic.AtomicReference;

/**
 * @author lenovo
 * 类说明:演示引用类型的原子操作类
 */
public class UseAtomicReference {

    static AtomicReference<UserInfo> userRef=new AtomicReference<UserInfo>();

    public static void main(String[] args) {
        UserInfo user=new UserInfo("zs",15);
        userRef.set(user);

        UserInfo updateUser=new UserInfo("ls",17);
        //这是候的更改是在新生成得对象进行变更
        userRef.compareAndSet(user,updateUser);
        System.out.println(userRef.get().getName());
        System.out.println(userRef.get().getAge());
        System.out.println(user.getName());
        System.out.println(user.getAge());


    }

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

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

        public String getName() {
            return name;
        }
        public int getAge() {
            return age;
        }


    }
}


结果展示(前两个值表示改变后引用的值,后两个值表示原来引用的值):
在这里插入图片描述

4) 更新字段类:AtomicReferenceFieldUpdater, AtomicIntegerFieldUpdater, AtomicLongFieldUpdater (很少使用)

解决ABA问题的两个类: AtomicMarkableReference 处理版本使用的是boolean (表示有没有动过)
AtomicStampedReference: 表示动过几次(它的构造函数为AtomicStampedReference(V initialRef, int initialStamp ) 第一个参数表示引用类型的值,第二参数表示版本号 )

扫描二维码关注公众号,回复: 8865976 查看本文章

简单代码示例(AtomicStampedReference具有较强的实用性,表示的是当我们遇到ABA问题时可采用版本号进行分辨后操作)
该代码中有两个线程对带有版本戳的类进行更改,其中一个线程是在版本正确的情况对引用类型的值进行更改(这种情况下是可以更改成功的)另外一个线程是在版本不正确的情况下对引用类型的值进行更改(这种情况下是更改失败的)
compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) 返回的结果值是Boolean表示更改成功或者失败
其中第一个参数表示期待的引用值,第二个表示新的引用值。
第三个参数表示的是期待的版本戳,第四个表示新的版本戳

package 原子操作;

import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * @author lenovo
 * 演示带版本戳的原子操作类
 */
public class UseAtomicStampedReference {

    //AtomicStampedReference(V initialRef, int initialStamp) 传初始值和版本号
    static AtomicStampedReference<String> asr=
            new AtomicStampedReference<String>("Mark",0);

    public static void main(String[] args) throws InterruptedException {
        final int oldStamp=asr.getStamp();
        final String oldReference=asr.getReference();

        System.out.println(oldReference+"======"+oldStamp);

        //正确版本号的线程
        Thread rightStampThread=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+
                        "当前变量值为:"+oldReference+" 当前版本号为:"+oldStamp+"-"
                        +asr.compareAndSet(oldReference,oldReference+" Java",
                        oldStamp,oldStamp+1));


            }
        });

        //错误版本号的线程
        Thread errorStampThread=new Thread(new Runnable() {
            @Override
            public void run() {

                System.out.println(Thread.currentThread().getName()+
                        "当前变量值为:"+asr.getReference()+" 当前版本号为:"+asr.getStamp()+"-"
                        +asr.compareAndSet(asr.getReference(),asr.getReference()+" C",
                        oldStamp,oldStamp+1));


            }
        });

        rightStampThread.start();
        rightStampThread.join();
        errorStampThread.start();
        errorStampThread.join();
        System.out.println(asr.getReference()+"======"+asr.getStamp());
    }
}

结果展示(第一行的值表示的是原本引用类型的值和版本号 第二行的值表示的是一个在正确版本戳下对引用类型值的改变
第三行的值表示的是在一个错误版本戳下对引用值类型的改变,第四行代表的更改后最终的结果值)
在这里插入图片描述

发布了19 篇原创文章 · 获赞 2 · 访问量 418

猜你喜欢

转载自blog.csdn.net/TheWindOfSon/article/details/103863601