源码:并发包-AtomicBoolean的源码解析

最近学习一下源码,这是并发包下边代码解析,简单直接一点直接上代码:

 /**
   * 一个简单的通过实现序列化的对象
   */
  public class AtomicBoolean implements java.io.Serializable {
    private static final long serialVersionUID = 4654671469794556979L;
    /**
     * Unsafe类,内部都是原生方法native,直接调用操作系统底层资源执行相应任务,
     * 它可以像C一样操作内存指针,是非线程安全的。Unsafe类官方并不对外开放,
     * 因为Unsafe这个类提供了一些绕开JVM的更底层功能,基于它的实现可以提高效率。
     */
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    /**
     * objectFieldOffset()方法用于获取某个字段相对Java对象的“起始地址”的偏移量,
     * 也提供了getInt、getLong、getObject之类的方法可以使用前面获取的偏移量来访问某个Java对象的某个字段。
     *
     * 补充解释一下:
     * 一个java对象可以看成是一段内存,各个字段都得按照一定的顺序放在这段内存里,同时考虑到对齐要求,
     * 可能这些字段不是连续放置的,用这个方法能准确地告诉你某个字段相对于对象的起始内存地址的字节偏移量,
     * 因为是相对偏移量,所以它其实跟某个具体对象又没什么太大关系,跟class的定义和虚拟机的内存模型的实现细节更相关。
     */
    static {
      try {
        valueOffset = unsafe.objectFieldOffset(AtomicBoolean.class.getDeclaredField("value"));
      } catch (Exception ex) { throw new Error(ex); }
    }

    /**
     * 要获取偏移量的属性
     */
    private volatile int value;

    /**
     * bool类型在内存中存储的就是0和1
     * 构造方法相当于赋初始值
     */
    public AtomicBoolean(boolean initialValue) {
      value = initialValue ? 1 : 0;
    }

    /**
     * Creates a new {@code AtomicBoolean} with initial value {@code false}.
     */
    public AtomicBoolean() {
    }

    /**
     * 根据0和1进行判断
     */
    public final boolean get() {
      return value != 0;
    }

    /**
     * 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
     * 如果成功就返回true,否则返回false,并且不修改原值。
     */
    public final boolean compareAndSet(boolean expect, boolean update) {
      int e = expect ? 1 : 0;
      int u = update ? 1 : 0;
      return unsafe.compareAndSwapInt(this, valueOffset, e, u);
    }

    /**
     *如果当前值 == 预期值,则以原子方式将该设置为给定的更新值。JSR规范中说:
     * 以原子方式读取和有条件地写入变量但不 创建任何 happen-before 排序,因此不提供与除 weakCompareAndSet 
     * 目标外任何变量以前或后续读取或写入操作有关的任何保证。大意就是说调用weakCompareAndSet时并不能保证不存在happen-before的发生
     * (也就是可能存在指令重排序导致此操作失败)。但是从Java源码来看,其实此方法并没有实现JSR规范的要求,
     * 最后效果和compareAndSet是等效的,都调用了unsafe.compareAndSwapInt()完成操作。
     */
    public boolean weakCompareAndSet(boolean expect, boolean update) {
      int e = expect ? 1 : 0;
      int u = update ? 1 : 0;
      return unsafe.compareAndSwapInt(this, valueOffset, e, u);
    }

    /**
     * 设置为给定值,直接修改原始值。
     */
    public final void set(boolean newValue) {
      value = newValue ? 1 : 0;
    }

    /**
     * 最后设置为给定值。 延时设置变量值,这个等价于set()方法,但是由于字段是volatile类型的,
     * 因此次字段的修改会比普通字段(非volatile字段)有稍微的性能延时(尽管可以忽略),
     * 所以如果不是想立即读取设置的新值,允许在“后台”修改值,那么此方法就很有用。
     * 如果还是难以理解,这里就类似于启动一个后台线程如执行修改新值的任务,原线程就不等待修改结果立即返回(这种解释其实是不正确的,但是可以这么理解
     */
    public final void lazySet(boolean newValue) {
      int v = newValue ? 1 : 0;
      unsafe.putOrderedInt(this, valueOffset, v);
    }

    /**
     * 以原子方式设置为给定值,并返回旧值。
     */
    public final boolean getAndSet(boolean newValue) {
      boolean prev;
      do {
        prev = get();
      } while (!compareAndSet(prev, newValue));
      return prev;
    }

    /**
     * Returns the String representation of the current value.
     * @return the String representation of the current value
     */
    public String toString() {
      return Boolean.toString(get());
    }

  }

核心CAS的介绍:

CAS:Compare and Swap
CAS(V, E, N)

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

实现源码:

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  //获取对象的变量的地址
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  //调用Atomic操作
//进入atomic.hpp,大意就是先去获取一次结果,如果结果和现在不同,就直接返回,因为有其他人修改了;否则会一直尝试去修改。直到成功。
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

CAS属于乐观锁,多个线程竞争只会有一个胜出,其余线程并不会挂起,而是继续尝试获取更新,当然也可以主动放弃。CAS操作是基于系统原语的(原语的执行必须是连续的,操作期间不会被系统中断,是一条CPU的原子指令),因此是一个不需要加锁的锁,也因此不可能出现死锁的情况。也就是说无锁操作天生免疫死锁。CAS操作中可能会带来的ABA问题:
假设这样一种场景,当第一个线程执行CAS(V,E,U)操作,在获取到当前变量V,准备修改为新值U前,另外两个线程已连续修改了两次变量V的值,使得该值又恢复为旧值,无法正确判断这个变量是否已被修改过,一般称这种情况为ABA问题。ABA问题一般不会有太大影响,产生几率也比较小。但是并不排除极特殊场景下会造成影响,因此需要解决方法:

1、AtomicStampedReference类
一个带有时间戳的对象引用,每次修改时,不但会设置新的值,还会记录修改时间。在下一次更新时,不但会对比当前值和期望值,还会对比当前时间和期望值对应的修改时间,只有二者都相同,才会做出更新。解决了反复读写时,无法预知值是否已被修改的窘境。底层实现为:一个键值对Pair存储数据和时间戳,并构造volatile修饰的私有实例;两者都符合预期才会调用Unsafe的compareAndSwapObject方法执行数值和时间戳替换。


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

参考:

深入浅出 Java Concurrency  http://www.blogjava.net/xylz/archive/2010/07/01/324988.html

unsafe.objectFieldOffset是什么? https://hllvm-group.iteye.com/group/topic/37940

Java中的CAS和Unsafe类 https://www.jianshu.com/p/df0585b61773

sun.misc.Unsafe详解和CAS底层实现 https://zhuanlan.zhihu.com/p/37579394

[死磕Java并发]—-深入分析CAS http://cmsblogs.com/?p=2235

发布了223 篇原创文章 · 获赞 308 · 访问量 84万+

猜你喜欢

转载自blog.csdn.net/maoyeqiu/article/details/93416116