一、概述  

  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. Spring缓存配置遇到的坑

    基本配置 <cache:annotation-driven cache-manager="cacheManager"/> <bean id="cache ...

  2. 关于adbd进程的ROOT权限问题

    http://blog.csdn.net/a345017062/article/details/6254402 adbd源码位于system/core/adb/目录下,可执行文件位于/sbin/adb ...

  3. 2018.06.29 洛谷P1505 [国家集训队]旅游(树链剖分)

    旅游 题目描述 Ray 乐忠于旅游,这次他来到了T 城.T 城是一个水上城市,一共有 N 个景点,有些景点之间会用一座桥连接.为了方便游客到达每个景点但又为了节约成本,T 城的任意两个景点之间有且只有 ...

  4. 2018.07.13 [HNOI2015]落忆枫音(容斥原理+dp)

    洛谷的传送门 bzoj的传送门 题意简述:在DAG中增加一条有向边,然后询问新图中一共 有多少个不同的子图为"树形图". 解法:容斥原理+dp,先考虑没有环的情况,经过尝试不难发现 ...

  5. ABP框架系列之七:(About-关于ABP)

    Considerations Source codes Contributors Contact ASP.NET Boilerplate is designed to help us to devel ...

  6. CentOS中的一些小技巧和特殊知识

    一:软件: firefox 1.在tab栏右键可以打开上一次关闭的标签. 2.在上面的搜索栏可以添加搜索引擎,这样就不需要再打开标签页访问搜索引擎主页来搜索了. 3.获取firefox下载弹框的资源U ...

  7. MySQL 分表和分区

    1.为什么需要分表和分区 在开发的过程中,经常会遇到大表的情况,所谓的大表是指存储了百万级乃至千万级条记录的表.这样的表过于庞大,导致数据库在查询和插入的时候耗时太长,如果涉及联合查询的情况,性能更加 ...

  8. 破解Oracle ERP密码

    前提:你有apps的数据库账户,想知道某个用户的密码,因为fnd_user中的密码为加密的,所以无法看懂,你可以尝试用下边的方式来查看用户密码. SQL> desc fnd_user; Name ...

  9. Android SDK目录结构

    Android版本下载:从4.0到8.0版本: Android SDK目录结构图: sdk全称:software develop kits 软件开发工具集 add-ons:Google API map ...

  10. Python学习-36.Python中的字典解释

    具体同列表解释,也是使用if来进行过滤 例子,生成一个新的字典,并且是原来字典的键值交换. mydict={'Tom':18,'Mary':20} print({value:key for key,v ...