美文网首页
无锁的并发策略(比较交换)

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

作者: 千释炎 | 来源:发表于2017-08-14 11:50 被阅读0次

  比较交换是一种无锁的并发策略,使用这种方式可以避免锁竞争带来的系统开销,以及线程间频繁调度带来的系统开销,因此具有比锁更好的性能。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,就直接抛出异常,拒绝访问。

相关文章

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

    比较交换是一种无锁的并发策略,使用这种方式可以避免锁竞争带来的系统开销,以及线程间频繁调度带来的系统开销,因此具有...

  • 无锁算法——CAS原理

    一、无锁算法 CAS(比较与交换,Compare and swap) 是一种有名的无锁算法。无锁编程,即不使用锁的...

  • 线程安全之无锁

    线程安全策略--CAS CAS与锁 CAS 全称 Compare and Swap ,中文名比较交换空间。JDK5...

  • 自旋锁

    简单回顾一下CAS算法 CAS算法即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程...

  • CAS算法

    前言 CAS,即 Compare And Swap(比较与交换),是一种无锁算法,基于硬件原语实现,能够在不使用锁...

  • 面试必备之深入理解自旋锁

    1、自旋锁 简单回顾一下CAS算法CAS算法 即compare and swap(比较与交换),是一种有名的无锁算...

  • 锁的优化及注意事项(二)

    一、无锁 对于并发控制而言,锁是一种悲观的策略,总是假设每一次进入临界区操作都会产生冲突,如果多线程访问临界区资源...

  • 二十四、Elasticsearch图解partial updat

    1、partial update乐观锁并发控制原理图 他内部会自动执行我们之前所说的乐观锁的并发控制策略。 2、r...

  • 关于CAS(乐观锁)的原理解析

    CAS(比较与交换,Compare and swap) 是一种有名的无锁算法,它是乐观锁的一种实现方式。所以在进行...

  • Java并发那些事儿-CAS

    CAS(Compare And Swap)比较与交换:一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程...

网友评论

      本文标题:无锁的并发策略(比较交换)

      本文链接:https://www.haomeiwen.com/subject/tzverxtx.html