JUC ---- 原子操作 CAS

1. CAS产生的背景

JDK其实在语言层级上就用Synchronized实现了线程安全,那为什么还要在1.5的版本后引入原子操作呢?
sync是基于阻塞的锁机制,它可能会带来以下问题:

  1. 被阻塞的线程优先级高。
  2. 拿到锁的线程一直不释放锁
  3. 大量的竞争,消耗CPU, 同时带来死锁或者其他安全问题。
2. CAS
2.1 简介

CAS (Compare And Swap), 是通过自旋实现的,它的底层是一个native方法(有C++ 语言编写,来控制JVM)。其最终的实现上是通过一条CPU指令实现的(大概意思上,就是若存在多核处理任务,则锁住总线)。这也从计算机的原语级别上保证了这个操作的原子性。

CAS操作中的三个运算符:
一个内存地址V, 一个期望值 A, 一个新值B
基本思路:如果地址V上的值和期望的值A相等,就给地址V赋上新值B。如果不是,就在循环(死循环,自旋)里不断进行CAS操作。

2.2 CAS遇到的问题
  • ABA问题: A—> B —> A
    原来的值是A, 但它进过更改后,先从A 变成B, 再从B变成了A。 那么CAS进行检查时发现它的值没有变化,但是实际上却变化了。(某些应用场景下是不允许这种变化的发生的)
    ABA问题的解决思路使用版本号,在变量前面追加版本号,每次变量更新的时候版本号加1,那么A-B-A 就会变为 1A-2B-3A。Java中提供了原子类AtomicStampedReference来解决这个问题(它表示动过几次),而AtomicMarkableReference则是使用boolean表示有没有动过。
  • 开销问题:CAS操作长期不成功,那么CPU会在这上面不断地自旋消耗大量资源。
  • 只能保证一个共享变量的原子操作
2.3 JDK中相关的原子操作类
  • 更新基本类型:AtomicBoolean, AtomicInteger, AtomicLong
  • 更新数组类型:AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray
  • 更新引用类型:AtomicReference, AtomicMarkableReference, AtomicStampedReference(解决ABA问题)
  • 原子更新字段类:AtomicReferenceFieldUpdater, AtomicIntegerFieldUpdater, AtomicLongFieldUpdater (使用相对较少)

由于使用方式比较类似,这里以AtomicStampedReference为例子进行演示

3. 原子类的使用

这里以两个线程为例子,线程1使用正确的版本戳修改完引用后,线程2再使用老的版本号进行修改,这时会发现修改失败(因为版本号不对应)

package atomic;

import java.util.concurrent.atomic.AtomicStampedReference;

public class UseAtomicStampedReference {
    //新建一个带版本号和引用类型的原子变量
    static AtomicStampedReference<String> asr = new AtomicStampedReference<>("gs",0);

    public static void main(String[] args) throws Exception {
        final int oldStamp = asr.getStamp(); //初始化的版本号
        final String oldReference = asr.getReference();

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

        //使用正确版本号的更改的线程
        Thread rightStamp = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()
                +"当前变量值:"+ oldReference + ",当前版本号为:"+oldStamp
                +"-"+asr.compareAndSet(oldReference,oldReference+" Love you",
                        oldStamp,oldStamp+1));
            }
        });
		//使用老版本号修改的线程
        Thread errorStamp = new Thread(new Runnable() {
        	//此时不能再使用oldStamp,因为版本号发生了变化
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()
                        +"当前变量值:"+ asr.getReference() + ",当前版本号为:"+asr.getStamp()
                        +"-"+asr.compareAndSet(asr.getReference(),asr.getReference()+" Love you",
                        oldStamp,oldStamp+1));
            }
        });

       rightStamp.start();
       //这里表示rightStamp的线程先运行完再运行errorStamp的线程
       rightStamp.join();
       errorStamp.start();
       errorStamp.join();

       System.out.println(asr.getReference()+"-----"+asr.getStamp());


    }
}

控制台的输出
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值