juc原子类

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了juc原子类相关的知识,希望对你有一定的参考价值。

0原子类

http://www.cnblogs.com/skywang12345/p/3514589.html
http://www.blogjava.net/xylz/archive/2010/07/01/324988.html

根据修改的数据类型,可以将JUC包中的原子操作类可以分为4类。

  1. 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;
  2. 数组类型: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray ;
  3. 引用类型: AtomicReference, AtomicStampedRerence, AtomicMarkableReference ;
  4. 对象的属性修改类型: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater 。

1 AtomicInteger

java.util.concurrent是基于Queue的并发包,而Queue,很多情况下使用到了Atomic操作。

通常情况下,在Java里面,++i或者--i不是线程安全的,这里面有三个独立的操作:或者变量当前值,为该值+1/-1,然后写回新的值。在没有额外资源可以利用的情况下,只能使用加锁才能保证读-改-写这三个操作时“原子性”的。

该类就是利用现代cpu特性CAS指令在不加锁的情况下实现i++,这种复合操作的原子性。

其中方法都比较好理解只有两个 lazyset和weakCompareAndSet 不理解。。。

  • 使用:
    例如实现一个全局的计数器,该类就能实现,其i++是原子执行。

1.8中新增几个类如LongAdder 改类和AtomicLong功能类似,只是更加高效。
http://ifeve.com/atomiclong-and-longadder/
技术分享


2 数组原子类

2.1 AtomicIntegerArray

对数组元素的原子更新,包括3个类。
此类的API和AtomicInteger 基本一致,只是修改元素时需要提供元素的索引。 实现几乎一样。

  • get() 分析
  1. public final int get(int i) {
  2. return unsafe.getIntVolatile(array, rawIndex(i));
  3. }
  4. private static final Unsafe unsafe = Unsafe.getUnsafe();
  5. private static final int base = unsafe.arrayBaseOffset(int[].class);
  6. private static final int scale = unsafe.arrayIndexScale(int[].class);
  7. private final int[] array;
  8. private long rawIndex(int i) {
  9. if (i < 0 || i >= array.length)
  10. throw new IndexOutOfBoundsException("index " + i);
  11. return base + (long) i * scale;
  12. }

base: java中数组对象,在存储时都包含一个对象头,这个头部长度可以使用arrayBaseOffset函数获得。
scale: 也就是能指明数组中每个元素占的字节长度。
base + (long) i * scale: 这样就能得出从数组对象头部开始,应该修改哪些字节的元素。
http://www.jb51.net/article/48318.htm

  • Unsafe
    该类是java中对于内存的底层操作(申请内存,查看某内存位置值等),但是一般我们不允许调用该类,该类单例模式实现,获取该类对象会检查类加载器,
    只有顶层类加载器才能调用,也就是一般我们获取不到该对象。

3 引用的原子类

3.1 AtomicReference

和基本类型的原子类似,都是包装一个类型,只是此处包装了引用类型V,注意 get set 修改的仅仅是引用, 并不是其引用指向的值。

此类方法也比较少,仅仅涉及到对引用的get set ;

  • ABA
    ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
    http://www.cnblogs.com/princessd8251/articles/5187403.html

3.2 AtomicMarkableReference 和AtomicStampedReference

各种乐观锁的实现中通常都会用版本戳version来对记录或对象标记,避免并发操作带来的问题,这两个类就是java用类似方法解决ABA。

  • 如何解决?
    其实二者类似,都是将该引用和一个状态变量包装到一个对象中,前者包装boolean后者int(类似上面所述1A 2A就能区分开来)。

3.2.1 AtomicStampedReference 实现

基于1.8 分析

  1. 创建一个类将引用V和状态值S打包在一起,这两个变量可以设置为final
  2. 将上面的包装类引用设计为volatile,这样每次更新V和S时,都会创建一个新的包装对象,然后对该引用CAS更新。

本质是将两个变量包装在一起,转化为基本类型变量原子更新。
1.8中

  1. private static class Pair<T> { // 对引用T和int值包装,
  2. final T reference;
  3. final int stamp;
  4. private Pair(T reference, int stamp) {
  5. this.reference = reference;
  6. this.stamp = stamp;
  7. }
  8. static <T> Pair<T> of(T reference, int stamp) {
  9. return new Pair<T>(reference, stamp);
  10. }
  11. }
  12. private volatile Pair<V> pair; //确保修改立即可见
  13. public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp,
  14. int newStamp) {
  15. Pair<V> current = pair;
  16. return
  17. expectedReference == current.reference && expectedStamp == current.stamp && casPair(current, Pair.of(newReference, newStamp))); // CAS 直接更新包装对象引用
  18. }
  19. private boolean casPair(Pair<V> cmp, Pair<V> val) { //CAS更新引用
  20. return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
  21. }

目标: 做到对引用T和int变量 原子更新(同时更改这两个变量);

  1. Pair类: 对两个变量的包装,两个变量都会设置为final,此处对于两个变量的更新每次都会创建新的Pair对象,所以这两个属性设置为final。
  2. compareAndSet(): 当更新两个变量时,创建新的PAIR然后CAS更新该引用。所以该引用pair必须设置为volatile
  • 示例
    场景:对储蓄卡充值 T表示剩余钱,stamp表示时间戳。 当前状态(10,0)
    线程1: cas((10,0),(20,1)) //即充值10元
    线程2: cas((10,0),(60,1)) //即充值50元

当同时调用compareAndSet时,二者会先判断当前状态是否是(10,0), 符合,所以两个线程都可能会创建新的Pair对象,1创建(20,1); 2创建(60,1),接下来就会调用casPair 更新当前pair引用, 两个线程虽然都已经创建新的Pair对象,但是只能有一个线程更新成功。 (此处问题就化简为对基本类型原子更新)

  • 1.6中:
    技术分享

1.6 中基本原理和1.8一致,只是更加繁琐,对于Pair对象引用的原子更新 又调用了AtomicReference来实现,而1.8则直接将该Pair(打包对象)设置为volatile


4 对象属性原子更新

4.1 AtomicIntegerFieldUpdater

可以对指定对象的volatile int‘类型的成员"进行原子更新。它是基于反射原理实现的。

该类是抽象类,具体的实现是由其内部类实现。

该类的实现和2中对数组元素的原子更新类似,

  • 数组i位置元素CAS更新时,依据传入索引i得出需要修改的内存位置(相对于数组起始位置),然后对该内存块调用底层CAS更新。

  • 该类同类,对对象a的int变量b CAS更新时,在创建该原子类时就会计算出变量b在a对象中的存储位置,更新时就会直接对该内存块CAS更新。
    技术分享

offset就是int 变量在该对象中的相对偏移值,在构造函数中就会计算出来。
构造函数中会检查需要更新的变量是否是volatile int,如果不是就会抛出异常、
技术分享


问题

  1. AtomicInteger中的lazyset ??
    http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6275329
    http://stackoverflow.com/questions/1468007/atomicinteger-lazyset-vs-set



















以上是关于juc原子类的主要内容,如果未能解决你的问题,请参考以下文章

ava多线程系列 JUC原子类 CAS及原子类

JUC原子类 1

JUC 中的 Atomic 原子类总结

Java——聊聊JUC中的原子变量类

Java——聊聊JUC中的原子变量类

java多线程系类:JUC原子类:05之AtomicIntegerFieldUpdater原子类