一、概述  

  Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。

  atomic包里的类基本都是使用Unsafe实现的包装类,在Atomic包里一共有12个类,四种原子更新方式,这里我们将对着四种方式进一步分析。

二、解析
  1. 原子更新基本类型类
  用于通过原子的方式更新基本类型,Atomic包提供了以下三个类:
    AtomicBoolean:原子更新布尔类型。
    AtomicInteger:原子更新整型。
    AtomicLong:原子更新长整型。

  这里我们着重介绍下AtomicInteger这个类的常用方法,如下:
  (1)int addAndGet(int delta) :

    以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
  (2)boolean compareAndSet(int expect, int update) :

    如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
  (3)int getAndIncrement():

    以原子方式将当前值加1,注意:这里返回的是自增前的值。
  (4)void lazySet(int newValue)
  (5)int getAndSet(int newValue):

    以原子方式设置为newValue的值,并返回旧值。

  参考实例如下:

      AtomicInteger a = new AtomicInteger(1);
a.addAndGet(2);
System.out.println(a); //3
a.getAndIncrement();
System.out.println(a.get()); //4

 这里我们延伸一下,介绍下CAS的实现原理:

  CAS(Compare and Swap,比较并操作),  CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

  CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

AtomicInteger中的 incrementAndGet 方法源码如下:

  public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}

   通过源码可以看到,方法执行步骤为:先获取到当前的 value 属性值,然后将 value 加 1,赋值给一个局部的 next 变量,然而,这两步都是非线程安全的,但是内部有一个死循环,不断去做compareAndSet操作,直到成功为止,也就是修改的根本在compareAndSet方法里面,compareAndSet()方法的代码如下:

    public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

  compareAndSwapInt 方法是一个native 方法,如下:

  public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
  
  所以compareAndSet传入的值为执行方法时获取到的value属性值,next为加 1 后的值,compareAndSet调用native的compareAndSwapInt方法来完成,
compareAndSwapInt 基于的是CPU的CAS指令来实现的。所以基于CAS的操作可认为是无阻塞的,一个线程的失败或挂起不会引起其它线程也失败或挂起。并且由于CAS操作是CPU原语,所以性能比较好。
  但是需要注意的是:在CAS操作中,会出现ABA的问题,即如果V值先由A变为B,再由B变为A,那么会仍然认为是发生了变化,并需要重新执行算法中的步骤。
  对应的解决方案是:不只更新某个引用的值,而是更新两个值,包括一个引用和一个版本号,即使这个值由A变为B,然后又变为A,版本号也是不同的。
  例如:AtomicMarkableReference和AtomicStampedReference支持在两个变量上执行原子的条件更新,AtomicStampedReference更新一个“对象-引用”二元组,通过在引用上加上“版本号”,从而避免ABA问题,AtomicMarkableReference将更新一个“对象引用-布尔值”的二元组。

 2. 原子更新数组类

  通过原子的方式更新数组里的某个元素,Atomic包提供了以下三个类:
    AtomicIntegerArray:原子更新整型数组里的元素,主要用于原子的方式更新数组里的整型。
    AtomicLongArray:原子更新长整型数组里的元素。
    AtomicReferenceArray:原子更新引用类型数组里的元素。

  这里我们同样着重介绍下AtomicIntegerArray类的常用方法,如下:

  (1)int addAndGet(int i, int delta):

    以原子方式将输入值与数组中索引i的元素相加。
  (2)boolean compareAndSet(int i, int expect, int update):

    如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。

  参考实例如下:

  int[] arr = new int[]{1,2,3};
  AtomicIntegerArray arr1= new AtomicIntegerArray(arr);
  arr1.getAndAdd(2,4);
  arr1.getAndSet(0,2);
  System.out.println(arr[2]); //3
  System.out.println(arr1.get(2)); //7

  注:AtomicIntegerArray对内部的数组元素进行修改时,不会影响到传入的数组,原因是数组值通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份。

 3. 原子更新引用类型

  原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子的更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了以下三个类:
    AtomicReference:原子更新引用类型。
    AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
    AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)

  参考实例如下:

  static class User{
String id;
String name;
volatile int age; public User(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
} public String getId() {
return id;
} public String getName() {
return name;
} public int getAge() {
return age;
}
  }
  public static void main(String[] args) {
User user1 = new User("1", "LiLei", 11);
User user2 = new User("2", "HanMeimei", 12); AtomicReference<User> user = new AtomicReference<User>(user1);
System.out.println(user1.getName()); //LiLei
user.getAndSet(user2);
System.out.println(user.get().getName()); //HanMeimei
  }

 4. 原子更新字段类

  如果我们只需要某个类里的某个字段,那么就需要使用原子更新字段类,Atomic包提供了以下三个类:
    AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
    AtomicLongFieldUpdater:原子更新长整型字段的更新器。
    AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题。
  原子更新字段类都是抽象类,每次使用都时候必须使用静态方法newUpdater创建一个更新器。原子更新类的字段的必须使用public volatile修饰符。

  AtomicIntegerFieldUpdater的例子代码如下:

  AtomicIntegerFieldUpdater<User> userUpdate = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
userUpdate.getAndIncrement(user2);
System.out.println(user2.getAge()); //13

  

  参考博文:

  http://ifeve.com/java-atomic/
  http://blog.csdn.net/qfycc92/article/details/46489553

JDK源码学习之 java.util.concurrent.automic包的更多相关文章

  1. JDK源码(1.7) -- java.util.Collection<E>

     java.util.Collection<E> 源码分析(JDK1.7) -------------------------------------------------------- ...

  2. JDK源码(1.7) -- java.util.Deque<E>

    java.util.Deque<E> 源码分析(JDK1.7) -------------------------------------------------------------- ...

  3. JDK源码(1.7) -- java.util.Queue<E>

    java.util.Queue<E> 源码分析(JDK1.7) -------------------------------------------------------------- ...

  4. JDK源码(1.7) -- java.util.Arrays

    java.util.Arrays 源码分析 ------------------------------------------------------------------------------ ...

  5. JDK源码(1.7) -- java.util.AbstractList<E>

    java.util.AbstractList<E> 源码分析(JDK1.7) ------------------------------------------------------- ...

  6. JDK源码(1.7) -- java.util.ListIterator<E>

    java.util.ListIterator<E> 源码分析(JDK1.7) ------------------------------------------------------- ...

  7. JDK源码(1.7) -- java.util.Iterator<E>

    java.util.Iterator<E> 源码分析(JDK1.7) ----------------------------------------------------------- ...

  8. JDK源码(1.7) -- java.util.List<E>

    java.util.List<E> 源码分析(JDK1.7) --------------------------------------------------------------- ...

  9. JDK源码(1.7) -- java.util.AbstractCollection<E>

    java.util.AbstractCollection<E> 源码分析(JDK1.7) ------------------------------------------------- ...

随机推荐

  1. OSGi 系列(十三)之 Configuration Admin Service

    OSGi 系列(十三)之 Configuration Admin Service OSGi 的 CM 就是 Configuration Admin Service,是用于管理 Bundle 属性.并在 ...

  2. common常用到的类

    org.apache.commons.codec.digest.DigestUtils.md5Hex(String)    md5

  3. mybatis 传参为 Integer 时 ,Mapper 文件 中判断 条件 问题。

    <if test="valiStatus==null || valiStatus=='' || valiStatus==4 "> b.work_permit_card_ ...

  4. 2018.10.15 bzoj3564: [SHOI2014]信号增幅仪(坐标处理+最小圆覆盖)

    传送门 省选考最小圆覆盖? 亦可赛艇(你们什么都没看见) 在大佬的引领下成功做了出来. 就是旋转坐标使椭圆的横轴跟xxx轴平行. 然后压缩横坐标使得其变成一个圆. 然后跑最小覆盖圆就可以了. 注意题目 ...

  5. 2018.07.08 hdu5316 Magician(线段树)

    Magician Problem Description Fantasy magicians usually gain their ability through one of three usual ...

  6. 37 The Benefits of Cutting Salt 减少盐分摄取量的益处

    The Benefits of Cutting Salt 减少盐分摄取量的益处 ①Just when you had figured out how to manage fat in your die ...

  7. [K8S]污点调度

    如果不希望某个节点被调度可以使用以下命令进行设置  kubectl taint node master01 node-role.kubernetes.io/master="":No ...

  8. PHP中的mb_convert_encoding与iconv函数介绍

    php传输乱码 mb_convert_encoding这个函数是用来转换编码的.原来一直对程序编码这一概念不理解,不过现在好像有点开窍了. 不过英文一般不会存在编码问题,只有中文数据才会有这个问题.比 ...

  9. 功率谱密度(PDS)的MATLAB分析

    功率谱密度(PSD),它定义了信号或者时间序列的功率如何随频率分布.这里功率可能是实际物理上的功率, 或者更经常便于表示抽象的信号被定义为信号数值的平方,也就是当信号的负载为1欧姆(ohm)时的实际功 ...

  10. C#-流、存储

    流输入输出 VS提供的类库,可方便数据的转移操作,一部分是文件和目录方面 file类 用静态方法,   fileinfo类 用实例方法   dictionary类 用静态方法   dictionary ...