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类。
- 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;
- 数组类型: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray ;
- 引用类型: AtomicReference, AtomicStampedRerence, AtomicMarkableReference ;
- 对象的属性修改类型: 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() 分析
public final int get(int i) {
return unsafe.getIntVolatile(array, rawIndex(i));
}
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int scale = unsafe.arrayIndexScale(int[].class);
private final int[] array;
private long rawIndex(int i) {
if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
return base + (long) i * scale;
}
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 分析
- 创建一个类将引用V和状态值S打包在一起,这两个变量可以设置为final
- 将上面的包装类引用设计为volatile,这样每次更新V和S时,都会创建一个新的包装对象,然后对该引用CAS更新。
本质是将两个变量包装在一起,转化为基本类型变量原子更新。
1.8中
private static class Pair<T> { // 对引用T和int值包装,
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);
}
}
private volatile Pair<V> pair; //确保修改立即可见
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference && expectedStamp == current.stamp && casPair(current, Pair.of(newReference, newStamp))); // CAS 直接更新包装对象引用
}
private boolean casPair(Pair<V> cmp, Pair<V> val) { //CAS更新引用
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
目标: 做到对引用T和int变量 原子更新(同时更改这两个变量);
- Pair类: 对两个变量的包装,两个变量都会设置为final,此处对于两个变量的更新每次都会创建新的Pair对象,所以这两个属性设置为final。
- 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,如果不是就会抛出异常、
问题
- AtomicInteger中的lazyset ??
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6275329
http://stackoverflow.com/questions/1468007/atomicinteger-lazyset-vs-set
以上是关于juc原子类的主要内容,如果未能解决你的问题,请参考以下文章