无锁的并发策略(比较交换)

  比较交换是一种无锁的并发策略,使用这种方式可以避免锁竞争带来的系统开销,以及线程间频繁调度带来的系统开销,因此具有比锁更好的性能。CAS算法如下:它包含3个参数CAS(V,E,N),V代表要更新的变量,E代表预期值,N代表新值。仅当V值等于E时,才会将V设置为N,如果V不等于N,说明有其他线程对V进行了操作,则当前线程什么也不做。因此每一次只有一个线程会操作成功,其余的均会失败,失败的线程并不会被挂起,只是被告知失败,并会继续尝试,直到操作成功。

  Java并发包中提供了一个atomic包,里面实现了一些CAS操作的线程安全的类型。

1.无锁的线程安全整数(AtomicInteger)内部实现分析(基于jdk1.8):

private volatile int value;
value代表AtomicInteger的值,用volatile修饰,保证了线程间的可见性

private static final long valueOffset;
valueOffset保存了value字段在AtomicInteger对象上的偏移量,通过valueOffset可以定位到AtomicInteger对象value字段的位置,其实现如下:
static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}

常用方法举例:

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
//当前值加1,并返回加1前的值

public final int getAndAdd(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta);
}
//当前值加delta,并返回先前的值

public final int getAndDecrement() {
    return unsafe.getAndAddInt(this, valueOffset, -1);
}
//当前值减1,并返回先前的值

public final int addAndGet(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
当前值加delta,并返回加delta之前的值

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
如果当前值与预期值(expect)相等,则将当前值更新为新值(update)

public float floatValue() {
    return (float)get();
}
将整型转化为float型

该类中的方法基本都调用unsafe实现,而unsafe中的方法底层采用c实现,比如:

public native boolean compareAndSwapInt(Object obj, long offset,int expect,int update);

类似的实现还有AtomicLong、AtomicBoolean、AtomicReference,此处不再赘述

2.存在的问题:

  使用CAS操作无法判断当前数据的状态,当某个线程修改了value,下一个线程又把它修改回上一个线程修改前的值,那么之后线程并不知道它曾被修改过。即常说的ABA问题。

  针对这种情况,JDK提供了AtomicStampedReference类,其内部同时维护对象值和一个时间戳。当AtomicStampedReference对象的值被修改时,除了要更新对象值之外,还要同时更新时间戳。当AtomicStampedReference设置对象值时,对象值和时间戳都要满足期望值,写入才会成功。因此即使对象值被写会原值,只要时间戳发生变化,就能防止不恰当的写入。

内部实现分析:

private volatile Pair<V> pair;

pair保存了对象值以及时间戳,Pair类源码如下:

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

以 compareAndSet方法为例:

 /**
  * Atomically sets the value of both the reference and stamp
  * to the given update values if the
  * current reference is {@code ==} to the expected reference
  * and the current stamp is equal to the expected stamp.
  *
  * @param expectedReference the expected value of the reference
  * @param newReference the new value for the reference
  * @param expectedStamp the expected value of the stamp
  * @param newStamp the new value for the stamp
  * @return {@code true} if successful
  */
public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
    Pair<V> current = pair;
        return
        expectedReference == current.reference &&
        expectedStamp == current.stamp &&
        ((newReference == current.reference &&
          newStamp == current.stamp) ||
         casPair(current, Pair.of(newReference, newStamp)));
}

当期望值与当前值相等,且期望时间戳和当前时间戳相等时,才会写入新值,并同时更新时间戳,casPair方法实现如下:

private boolean casPair(Pair<V> cmp, Pair<V> val) {
    return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}

3.不安全类(unsafe)

  Java不能直接访问操作系统底层,而是通过本地方法来访问,其中封装了一些类似指针的操作,使用unsafe类可以分配内存,释放内存,类中提供3个本地方法allocateMemory、reallocateMemory、freeMemory分别用于分配内存,扩充内存和释放内存

unsafe主要方法如下:

//获得给定对象偏移量上的int值
private native int getInt(Object o,long offset);
//设置给定对象偏移量上的int值
private native int putInt(Object o,long offset,int x);
//获得字段在对象上的偏移量
private native long objectFieldOffset(Field f);
//设置给定对象的int值,使用volatile语义
private native void putIntVolatile(Object o,long offset,int x);
//获得的给定对象的int值,使用volatile语义
private native int getIntVolatile(Object o,long offset);
//和putIntVolatile一样,不过它要求被操作字段就是volatile类型的
private native void putOrderedInt(Object o,long offset,int x);

  JDK开发人员并不希望大家使用这个类,获得unsafe实例的方法是调用其工厂方法getUnsafe(),其实现如下:

public static Unsafe(){
   Class cc=Reflection.getCallerClass();
   if(cc.getClassLoader()!=null)
      throw new SecurityException("Unsafe");
   return theUnsafe;
}

  调用该方法时,它会检查调用该方法的类,如果这个类的ClassLoader不为null,就直接抛出异常,拒绝访问。

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

推荐阅读更多精彩内容