Java中的CAS和Unsafe类

CAS:Compare and Swap

CAS(V, E, N)

  • V:要更新的变量
  • E:预期值
  • N:新值
    如果V值等于E值,则将V值设为N值;如果V值不等于E值,说明其他线程做了更新,那么当前线程什么也不做。(放弃操作或重新读取数据)

CAS属于乐观锁,多个线程竞争只会有一个胜出,其余线程并不会挂起,而是继续尝试获取更新,当然也可以主动放弃。CAS操作是基于系统原语的(原语的执行必须是连续的,操作期间不会被系统中断,是一条CPU的原子指令),因此是一个不需要加锁的锁,也因此不可能出现死锁的情况。也就是说无锁操作天生免疫死锁。

Unsafe类

java中CAS操作依赖于Unsafe类,Unsafe类所有方法都是native的,直接调用操作系统底层资源执行相应任务,它可以像C一样操作内存指针,是非线程安全的。

Unsafe里的CAS 操作相关

Java中无锁操作CAS基于以下3个方法实现:

//第一个参数o为给定对象,offset为对象内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值,
//expected表示期望值,x表示要设置的值,下面3个方法都通过CAS原子指令执行操作。
public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);                                                                                                   
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x); 
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);

Atomic系列内部方法是基于下述方法的实现的。

并发包中的原子操作类(Atomic系列)

并发包中的原子操作类提供了许多基于CAS实现的原子操作类。

原子更新基本类型
  • AtomicBoolean:原子更新布尔类型
  • AtomicInteger:原子更新整型
  • AtomicLong:原子更新长整型

下面看AtomicInteger类的源码:

public class AtomicInteger extends Number implements java.io.Serializable {    
    private static final long serialVersionUID = 6214790243416807050L;     
    // 获取指针类Unsafe    
    private static final Unsafe unsafe = Unsafe.getUnsafe();     
    //下述变量value在AtomicInteger实例对象内的内存偏移量    
    private static final long valueOffset;     
    static {        
          try {           
              //通过unsafe类的objectFieldOffset()方法,获取value变量在对象内存中的偏移           
              //通过该偏移量valueOffset,unsafe类的内部方法可以获取到变量value对其进行取值或赋值操作            
              valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));        
          } catch (Exception ex) { throw new Error(ex); }    
    }   
    //当前AtomicInteger封装的int变量value    
    private volatile int value;     
    public AtomicInteger(int initialValue) {        
        value = initialValue;    
    }    
    public AtomicInteger() {   
    }   
    //获取当前最新值    
    public final int get() {        
        return value;    
    }    
    //设置当前值,具备volatile效果,方法用final修饰是为了更进一步的保证线程安全。    
    public final void set(int newValue) {        
        value = newValue;    
    }    
    //最终会设置成newValue,使用该方法后可能导致其他线程在之后的一小段时间内可以获取到旧值,有点类似于延迟加载    
    public final void lazySet(int newValue) {        
        unsafe.putOrderedInt(this, valueOffset, newValue);    
    }   
    //设置新值并获取旧值,底层调用的是CAS操作即unsafe.compareAndSwapInt()方法    
    public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);    
    }   
    //如果当前值为expect,则设置为update(当前值指的是value变量)    
    public final boolean compareAndSet(int expect, int update) { 
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);    
    }    
    //当前值加1返回旧值,底层CAS操作    
    public final int getAndIncrement() { 
        return unsafe.getAndAddInt(this, valueOffset, 1);   
    }    
    //当前值减1,返回旧值,底层CAS操作    
    public final int getAndDecrement() {        
        return unsafe.getAndAddInt(this, valueOffset, -1);    
    }   
    //当前值增加delta,返回旧值,底层CAS操作    
    public final int getAndAdd(int delta) {        
          return unsafe.getAndAddInt(this, valueOffset, delta);    
    }    
    //当前值加1,返回新值,底层CAS操作    
    public final int incrementAndGet() {        
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;    
    }    
    //当前值减1,返回新值,底层CAS操作    
    public final int decrementAndGet() {        
        return unsafe.getAndAddInt(this, valueOffset, -1) - 1;    
    }   
    //当前值增加delta,返回新值,底层CAS操作    
    public final int addAndGet(int delta) {        
        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;    
    }   
    //省略一些不常用的方法....
 }

AtomicInteger基本是基于Unsafe类中CAS相关操作实现的,是无锁操作。

public final int getAndIncrement() { 
    return unsafe.getAndAddInt(this, valueOffset, 1);   
}

调用了Unsafe类中的getAndAddInt()方法,该方法执行一个CAS操作,保证线程安全。

//Unsafe类中的getAndAddInt方法
public final int getAndAddInt(Object o, long offset, int delta) {        
    int v;        
    do {            
        v = getIntVolatile(o, offset);        
    } while (!compareAndSwapInt(o, offset, v, v + delta));        
    return v;
}

可看出getAndAddInt通过一个while循环不断的重试更新要设置的值,直到成功为止,调用的是Unsafe类中的compareAndSwapInt方法,是一个CAS操作方法。

上述源码分析是基于JDK1.8的,如果是1.8之前的方法,1.8之前的方法实现如下:

//JDK 1.7的源码,由for的死循环实现,并且直接在AtomicInteger实现该方法,
//JDK1.8后,该方法实现已移动到Unsafe类中,直接调用getAndAddInt方法即可
public final int incrementAndGet() {    
    for (;;) {        
        int current = get();        
        int next = current + 1;        
        if (compareAndSet(current, next))            
            return next;    
    }
}

CAS操作中可能会带来的ABA问题

假设这样一种场景,当第一个线程执行CAS(V,E,U)操作,在获取到当前变量V,准备修改为新值U前,另外两个线程已连续修改了两次变量V的值,使得该值又恢复为旧值:



无法正确判断这个变量是否已被修改过,一般称这种情况为ABA问题。

ABA问题一般不会有太大影响,产生几率也比较小。但是并不排除极特殊场景下会造成影响,因此需要解决方法:

  • AtomicStampedReference类
  • AtomicMarkableReference类
AtomicStampedReference类

一个带有时间戳的对象引用,每次修改时,不但会设置新的值,还会记录修改时间。在下一次更新时,不但会对比当前值和期望值,还会对比当前时间和期望值对应的修改时间,只有二者都相同,才会做出更新。解决了反复读写时,无法预知值是否已被修改的窘境。

底层实现为:一个键值对Pair存储数据和时间戳,并构造volatile修饰的私有实例;两者都符合预期才会调用Unsafe的compareAndSwapObject方法执行数值和时间戳替换。

AtomicMarkableReference类

一个boolean值的标识,true和false两种切换状态表示是否被修改。不靠谱。

自旋锁

自旋锁:假设在不久的将来,线程可以获得锁,虚拟机会让线程做几个空循环(自旋),若干次循环、尝试获得锁后,如果获得锁,线程进入临界区;如果没有成功,则被挂起。

避免了线程在尝试获得锁失败后,挂起,再次尝试之间,状态不断转换造成的资源浪费,提升效率。

但如果有太多线程进入自旋状态,CPU的占用时间(自选过程)会过长,反而使得效率变低,性能下降。因此,虚拟机限制了自旋次数(几十次至100次),这之后虚拟机会将线程挂起,让出cpu资源。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容