JUC原子类
文章目录
一、分类
Juc原子类可以分为以下四大类:
- 基本类型
- AtomicInteger, AtomicLong, AtomicBoolean
- 数组类型
- AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray
- 引用类型
- AtomicReference, AtomicStampedRerence, AtomicMarkableReference
- 对象属性类型
- AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater
二、基本类型
1、AtomicInteger
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
/*
* This class intended to be implemented using VarHandles, but there
* are unresolved cyclic startup dependencies.
*/
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
private static final long VALUE;
static {
try {
//valueOffset,也就是内存偏移量。偏移量在AtomicInteger中很重要,AtomicInteger的原子操作都靠内存偏移量来实现的。
VALUE = U.objectFieldOffset
//获取value变量的offset
(AtomicInteger.class.getDeclaredField("value"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
/**
* 使用了volatile,保证多线程的可见性。但是会禁止重排序等优化,因此会下降性能。
* 非高并发不要使用。
*/
private volatile int value;
}
对于使用了volatile,其实就是为了处理多线程对于共享变量的可见性,保证数据的及时性。但是,我们也知道,做一个加法的过程中,实际上是一个load->update->writeback,这里我们保证了load是相同,但是update以及后面的回写就会出现线程安全问题,因此,volatile只能用于保证第一步的load是安全的,及时的。
那么,接下来我们就得想办法处理,update这个过程,让它也是安全的,这样就能够实现原子操作。
-
加锁(悲观)
很明显,这个能够通过加锁解决,但是,锁的性能消耗太多了,以至于哪怕没有线程竞争,也会消耗性能,果断放弃。
-
CAS(乐观)
先假定没有冲突直接进行操作,如果因为有冲突而失败就重试,直到操作成功。其中乐观锁用到的机制就是CAS,Compare and Swap。
具体的操作就是:
- 假定没有冲突,就是直接修改,这个时候如果没有冲突,那么很明显,性能提上来了。
- 如果发生冲突,那么进行重试,判断expect与内存中的value是否相同,如果相同,执行修改。
看一下源码的实现:
public final int getAndIncrement() {
//VALUE就是value的offset.
return U.getAndAddInt(this, VALUE, 1);
}
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
//获取当前的值
v = getIntVolatile(o, offset);
//如果交换失败,则进行重试。
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
//该方法参数为offset, v:前者是内存偏移量,v是当前值;如果两者相等,就加1。
//如果不等,证明内存值被改变,重新获取当前值,再进行比较。
weakCompareAndSetInt(o, offset, v, v + delta);
总结:
- volatile保证可见性
- 每次修改之前获取一下当前值。
- 实际操作的时候比较一下一样不一样,不一样就重试(重新获取当前值),一样就修改。
2、AtomicLong
在学习了AtomicInteger之后,姑且猜测Long也是跟Integer相同的实现原理,当然,可能会有些许不同。直接上源码吧。
public class AtomicLong extends Number implements java.io.Serializable {
private static final long serialVersionUID = 1927816293512124184L;
/**
* Records whether the underlying JVM supports lockless
* compareAndSet for longs. While the intrinsic compareAndSetLong
* method works in either case, some constructions should be
* handled at Java level to avoid locking user-visible locks.
*/
static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
/**
* Returns whether underlying JVM supports lockless CompareAndSet
* for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS.
*/
private static native boolean VMSupportsCS8();
/*
* This class intended to be implemented using VarHandles, but there
* are unresolved cyclic startup dependencies.
*/
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
private static final long VALUE;
static {
try {
VALUE = U.objectFieldOffset
(AtomicLong.class.getDeclaredField("value"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
private volatile long value;
}
既然起始的部分都是通过volatile与内存偏移量来处理,那么,实现的底层也是CAS,如下:
public final long getAndIncrement() {
return U.getAndAddLong(this, VALUE, 1L);
}
@HotSpotIntrinsicCandidate
public final long getAndAddLong(Object o, long offset, long delta) {
long v;
do {
v = getLongVolatile(o, offset);
} while (!weakCompareAndSetLong(o, offset, v, v + delta));
return v;
}
3、AtomicBoolean
原子布尔类型,本质上也是通过CAS实现,不过,我们还是通过源码看看有什么不同。
public class AtomicBoolean implements java.io.Serializable {
private static final long serialVersionUID = 4654671469794556979L;
private static final VarHandle VALUE;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
VALUE = l.findVarHandle(AtomicBoolean.class, "value", int.class);
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
private volatile int value;
}
令我有点意外的是,原子布尔类的实现居然是使用0,1作为底层实现,而非boolean。
public final boolean compareAndSet(boolean expectedValue, boolean newValue) {
return VALUE.compareAndSet(this,
(expectedValue ? 1 : 0),
(newValue ? 1 : 0));
}
可以看到,我们传入的boolean类型的参数会被转化成为int类型。
三、数组类型
读完上面的原子类实现之后,其实可以发现,dougLea在设计这些原子类的时候,都是采用了CAS的思想,但是对于不同的原子类,它会有不同的细节实现,以下是原子数组,这里只列举原子整型数组,对于其它数组,有兴趣的可以自己去阅读源码。
1、AtomicIntegerArray
public class AtomicIntegerArray implements java.io.Serializable {
private static final long serialVersionUID = 2862133569453604235L;
private static final VarHandle AA
= MethodHandles.arrayElementVarHandle(int[].class);
//使用final,而不使用volatile,原因是volatile也无法保证元素的可见性。
//而final可以保证元素是经过初始化,且具有地址的不变性。
//对于数组的元素,是在JNI层次使用volatile.
private final int[] array;
/**
* Creates a new AtomicIntegerArray of the given length, with all
* elements initially zero.
*
* @param length the length of the array
*/
public AtomicIntegerArray(int length) {
array = new int[length];
}
/**
* Creates a new AtomicIntegerArray with the same length as, and
* all elements copied from, the given array.
*
* @param array the array to copy elements from
* @throws NullPointerException if array is null
*/
public AtomicIntegerArray(int[] array) {
// Visibility guaranteed by final field guarantees
this.array = array.clone();
}
}
其实我很惊讶,居然不是
private volatile int[] array;
而是
private final int[] array;
通过查找资料,原来,private volatile int[] array并不能够使得数组中的元素具有内存可见性,它只能使得array这个数组变量是具有可见性的。
于是,探究为什么是采用final?
-
final的内存语义:
- final指向的对象是不能变更地址的。
- final对象使用时,必须是经过初始化的。
虽然了解了final语义,但是还是有点不明白,那就读源码吧。
public final void set(int i, int newValue) {
AA.setVolatile(array, i, newValue);
}
懂了,对于原子数组类型的实现,底层还是volatile加cas,虽然数组没有标明是volatile(使用volatile标识数组也没有丝毫作用),而是采用了final,确保使用时,对象都是经过初始化的。
在JNI实现时,还是使用了setVolatile,也就是底层还是调用volatile语义去实现。再配合CAS,实现指定的数组元素的原子性操作。
四、引用类型
1、AtomicReference
以上已经学了数组、单一类型两种,那么对于引用类型来说,也不会难理解。
public class AtomicReference<V> implements java.io.Serializable {
private static final long serialVersionUID = -1848883965231344442L;
private static final VarHandle VALUE;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
VALUE = l.findVarHandle(AtomicReference.class, "value", Object.class);
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
private volatile V value;
}
可以很明显的看出,引用类型无非就是采用volatile修饰对应的对象。并且在替换、改变的时候使用CAS作为实现。
2、AtomicReferenceArray
参考AtomicIntegerArray,可以知道,原子引用数组也是采用final修饰数组变量,volatile加CAS作为底层实现。
五、对象属性类型
在学习了以上的原子类之后,我们会发现,它们真的很好用,但是,有时候我们的代码已经写好了,只是想在某项业务中将原本的非原子性的属性改为具有原子性,怎么搞?dougLea已经想好,并且设计出了针对某些类的非原子属性进行原子性操作的,就是AtomicXXFieldUpdater。
1、AtomicIntegerFieldUpdater(抽象类)
AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
final String fieldName,
final Class<?> caller) {
final Field field;
final int modifiers;
try {
field = AccessController.doPrivileged(
new PrivilegedExceptionAction<Field>() {
public Field run() throws NoSuchFieldException {
return tclass.getDeclaredField(fieldName);
}
});
modifiers = field.getModifiers();
sun.reflect.misc.ReflectUtil.ensureMemberAccess(
caller, tclass, null, modifiers);
ClassLoader cl = tclass.getClassLoader();
ClassLoader ccl = caller.getClassLoader();
if ((ccl != null) && (ccl != cl) &&
((cl == null) || !isAncestor(cl, ccl))) {
sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
}
} catch (PrivilegedActionException pae) {
throw new RuntimeException(pae.getException());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
if (field.getType() != int.class)
throw new IllegalArgumentException("Must be integer type");
if (!Modifier.isVolatile(modifiers))
throw new IllegalArgumentException("Must be volatile type");
// Access to protected field members is restricted to receivers only
// of the accessing class, or one of its subclasses, and the
// accessing class must in turn be a subclass (or package sibling)
// of the protected member's defining class.
// If the updater refers to a protected field of a declaring class
// outside the current package, the receiver argument will be
// narrowed to the type of the accessing class.
this.cclass = (Modifier.isProtected(modifiers) &&
tclass.isAssignableFrom(caller) &&
!isSamePackage(tclass, caller))
? caller : tclass;
this.tclass = tclass;
this.offset = U.objectFieldOffset(field);
}
实现:
- 通过反射,拿到对应的字段进行封装,并且调用封装后的一些操作方法。
- 这一系列的操作方法的底层都是CAS。
注意:对应的字段必须是volatile,这点我想无需多说了。
demo
public class AtomicField {
//内存可见性,必须保证。
private volatile int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
/**
* 不是原子操作,不安全。
*/
public void unSafeIncre() {
value += 1;
}
public static void main(String[] args) {
AtomicField atomicField = new AtomicField();
//通过构造updater
AtomicIntegerFieldUpdater<AtomicField> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicField.class, "value");
updater.addAndGet(atomicField, 1000);
System.out.println(updater.get(atomicField));
}
}