java.util.concurrent.Atomic包下定义了很多具有原子操作的类
先看看Atomic包下常用的AtomicInteger类
package java.util.concurrent.atomic;
import java.lang.invoke.VarHandle;
import java.util.function.IntBinaryOperator;
import java.util.function.IntUnaryOperator;
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// JDK私有的Unsafe类,可以操作系统内存,提供一些基于CAS实现的原子操作方法,保证value线程安全
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
// 通过Unsafe实例U获取value字段在内存地址的偏移量
private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
// AtomicInteger维护的int value, 被volatile修饰,保证线程可见和有序性,私有的不能直接获取,需要通过get()方法获取
private volatile int value;
// 构造函数,设置初始值
public AtomicInteger(int initialValue) {
value = initialValue;
}
// 若不指定初始值,则默认0
public AtomicInteger() {
}
// 获取value
public final int get() {
return value;
}
// 给value赋值,由于volatile保证可见性和有序性,对value赋值会插入内存屏障,强制将新数据立即刷入内存
public final void set(int newValue) {
value = newValue;
}
// 直接获取内存中value值
public final int getPlain() {
return U.getInt(this, VALUE);
}
// 直接设置内存中value值
public final void setPlain(int newValue) {
U.putInt(this, VALUE, newValue);
}
// lazySet方法则是通过Unsafe实例调用putIntRelease进而调用putIntVolatile方法直接在value字段所在内存地址上去赋值,没有使用内存屏障,提高程序的执行效率,但是不能保证可见性(没有从工作线程刷新到内存这个操作,总线嗅探不到,其他线程仍然可能操作自己的工作线程中的数据)
public final void lazySet(int newValue) {
U.putIntRelease(this, VALUE, newValue);
}
// 以原子方式设置为给定值,并返回旧值
public final int getAndSet(int newValue) {
return U.getAndSetInt(this, VALUE, newValue);
}
// 在内存上,CAS设置value
public final boolean compareAndSet(int expectedValue, int newValue) {
return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
}
// 方法过期,建议用weakCompareAndSetPlain
@Deprecated(since="9")
public final boolean weakCompareAndSet(int expectedValue, int newValue) {
return U.weakCompareAndSetIntPlain(this, VALUE, expectedValue, newValue);
}
// weakCompareAndSetPlain也是CAS但是移除了volatile变量操作前后的内存屏障,因此不能保证有序性
public final boolean weakCompareAndSetPlain(int expectedValue, int newValue) {
return U.weakCompareAndSetIntPlain(this, VALUE, expectedValue, newValue);
}
// 以原子方式将当前值加 1,并返回旧值
public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}
// 以原子方式将当前值加 1,并返回新值
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
// 以原子方式将当前值减 1,并返回旧值
public final int getAndDecrement() {
return U.getAndAddInt(this, VALUE, -1);
}
// 以原子方式将当前值减 1,并返回新值
public final int decrementAndGet() {
return U.getAndAddInt(this, VALUE, -1) - 1;
}
// 以原子方式将当前值增加指定值delta,并返回旧值
public final int getAndAdd(int delta) {
return U.getAndAddInt(this, VALUE, delta);
}
// 以原子方式将当前值增加指定值delta,并返回新值
public final int addAndGet(int delta) {
return U.getAndAddInt(this, VALUE, delta) + delta;
}
// 使用IntUnaryOperator接口的方法修改value,并返回旧值
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev = get(), next = 0;
for (boolean haveNext = false;;) {
if (!haveNext)
// updateFunction接口的抽象applyAsInt方法,可以在主程序中通过该接口的内部类对象实现,计算得到新的值,通过weakCompareAndSetVolatile修改内存中的value
next = updateFunction.applyAsInt(prev);
if (weakCompareAndSetVolatile(prev, next))
return prev; //返回旧值
haveNext = (prev == (prev = get()));
}
}
// 使用IntBinaryOperator 接口的applyAsInt方法修改value, 并返回旧值,和getAndUpdate不同的是IntBinaryOperator接口的applyAsInt方法有两个参数
public final int getAndAccumulate(int x,
IntBinaryOperator accumulatorFunction) {
int prev = get(), next = 0;
for (boolean haveNext = false;;) {
if (!haveNext)
next = accumulatorFunction.applyAsInt(prev, x);
if (weakCompareAndSetVolatile(prev, next))
return prev;
haveNext = (prev == (prev = get()));
}
}
// 使用IntUnaryOperator接口的方法修改value,并返回新值
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev = get(), next = 0;
for (boolean haveNext = false;;) {
if (!haveNext)
next = updateFunction.applyAsInt(prev);
if (weakCompareAndSetVolatile(prev, next))
return next;
haveNext = (prev == (prev = get()));
}
}
// 使用IntBinaryOperator 接口的applyAsInt方法修改value, 并返回新值,和updateAndGet不同的是IntBinaryOperator接口的applyAsInt方法有两个参数
public final int accumulateAndGet(int x,
IntBinaryOperator accumulatorFunction) {
int prev = get(), next = 0;
for (boolean haveNext = false;;) {
if (!haveNext)
next = accumulatorFunction.applyAsInt(prev, x);
if (weakCompareAndSetVolatile(prev, next))
return next;
haveNext = (prev == (prev = get()));
}
}
// ...
}
重点私有属性
1. private volatile int value; // AtomicInteger维护的int value, 被volatile修饰,保证线程可见和有序性;
2. private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe(); //JDK私有的Unsafe类,可以操作系统内存,提供一些基于CAS实现的原子操作方法,保证value操作的原子性;
3. private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value"); // 通过Unsafe实例U获取value字段在内存地址的偏移量,可以勇敢Unsafe实例U直接操作内存上的value;
重点实现方法
方法 | 方法说明 |
---|---|
| 获取value的值 |
| 设置value的值,volatile保证可见性和有序性,也单独的写操作的原子性 |
| lazySet方法则是通过Unsafe实例调用putIntRelease进而调用putIntVolatile方法直接在value字段所在内存地址上去赋值,没有使用内存屏障,提高程序的执行效率,但是不能保证可见性(没有从工作线程刷新到内存这个操作,总线嗅探不到,其他线程仍然可能操作自己的工作线程中的数据) |
| 通过Unsafe实例和value字段在内存地址的偏移量VALUE直接获取内存上的value |
| 通过Unsafe实例调用putInt()方法直接修改内存上的value |
| 以原子方式设置为给定值,并返回旧值 |
| 直接在内存上通过CAS修改value |
| weakCompareAndSetPlain也是CAS但是移除了volatile变量操作前后的内存屏障,因此不能保证有序性 |
| 原子自增1,并返回旧值 |
| 原子自增1,并返回新值 |
| 原子自减1,并返回旧值 |
| 原子自减1,并返回新值 |
| 原子自增delta,并返回旧值 |
| 原子自增delta,并返回新值 |
| 实现IntUnaryOperator接口的方法修改value,并返回旧值 |
| 实现IntUnaryOperator接口的方法修改value,并返回新值 |
| 实现IntBinaryOperator接口的方法修改value,并返回旧值 |
| 实现IntBinaryOperator接口的方法修改value,并返回新值 |
自增自减的几个方法都是通过Unsafe实例调用getAndAddInt方法修改value:
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
// 自旋
do {
// 获取内存上的value
v = getIntVolatile(o, offset);
// CAS
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
// 返回旧值
return v;
}
AtomicInteger优点
1. 乐观锁,通过CAS保证读写操作的原子性,保证线程安全;
AtomicInteger缺点
1. 自旋,占CPU资源;
2. 存在CAS的ABA问题。
AtomicLong和AtomicInteger的原理一致,都是通过Unsafe类和CAS保证value线程安全
AtomicBoolean内部则是使用VarHandle代替Unsafe, VarHandle主要是提供Atomic和Unsafe相似的功能,但会更加安全和易用,并且在并发方面提高了性能。在Java并发包中,官方现在已不推荐再使用Unsafe这个类了,例如在AtomicIntegerArray,AtomicLongArray,AtomicReference和AtomicReferenceArray中已经用VarHandle替换了Unsafe,并且在CAS中也会用到。
public class AtomicIntegerArray implements java.io.Serializable {
// VarHandle 替换Unsafe类,来实现array的线程安全
private static final VarHandle AA
= MethodHandles.arrayElementVarHandle(int[].class);
// AtomicIntegerArray维护的是一个int数组
private final int[] array;
// ...
}
解决CAS的ABA问题的Atomic类:AtomicStampedReference和AtomicMarkableReference
public class AtomicStampedReference<V> {
// Pair<T> 给reference绑定一个stamp变量,用stamp来表示reference被修改的次数,相当于记录reference的版本号,线程在比较reference预期值和内存值相同后还需要比较stamp相同,才能进行修改
private static class Pair<T> {
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);
}
}
// ...
}
public class AtomicMarkableReference<V> {
// Pair<T> 给reference绑定一个mark变量,用mark来标记在拿到reference和比较内存值期间reference是否被修改过,线程在比较reference预期值和内存值相同后还需要比较mark为false,才能进行修改
private static class Pair<T> {
final T reference;
final boolean mark;
private Pair(T reference, boolean mark) {
this.reference = reference;
this.mark = mark;
}
static <T> Pair<T> of(T reference, boolean mark) {
return new Pair<T>(reference, mark);
}
}
}
再来看看Atomic包下一个抽象类:Striped64
package java.util.concurrent.atomic;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.DoubleBinaryOperator;
import java.util.function.LongBinaryOperator;
@SuppressWarnings("serial")
abstract class Striped64 extends Number {
@jdk.internal.vm.annotation.Contended static final class Cell {
// 保存要累加的值
volatile long value;
Cell(long x) { value = x; }
// VALUE是VarHandle类的实例,调用compareAndSet方法,CAS更新value
final boolean cas(long cmp, long val) {
return VALUE.compareAndSet(this, cmp, val);
}
final void reset() {
VALUE.setVolatile(this, 0L);
}
final void reset(long identity) {
VALUE.setVolatile(this, identity);
}
final long getAndSet(long val) {
return (long)VALUE.getAndSet(this, val);
}
private static final VarHandle VALUE;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
VALUE = l.findVarHandle(Cell.class, "value", long.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
}
// cpu数量, 主要在cell数组扩容中参考使用
static final int NCPU = Runtime.getRuntime().availableProcessors();
// cell表,容量为2的次幂
transient volatile Cell[] cells;
// 基础值,在更新操作时基于CAS无锁技术实现原子更新
transient volatile long base;
// 自旋锁 用于保护创建或者扩展Cell表
transient volatile int cellsBusy;
// 获取base成员变量相对于Striped64对象头的偏移地址BASE,cellsBusy这个自旋锁相对于Striped64对象头的偏移地址CELLSBUSY,和threadLocalRandomProbe相对于Thread类对象头的偏移地址THREAD_PROBE
private static final VarHandle BASE;
private static final VarHandle CELLSBUSY;
private static final VarHandle THREAD_PROBE;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
BASE = l.findVarHandle(Striped64.class,
"base", long.class);
CELLSBUSY = l.findVarHandle(Striped64.class,
"cellsBusy", int.class);
l = java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<>() {
public MethodHandles.Lookup run() {
try {
return MethodHandles.privateLookupIn(Thread.class, MethodHandles.lookup());
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}});
THREAD_PROBE = l.findVarHandle(Thread.class,
"threadLocalRandomProbe", int.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
Striped64() {
}
//cas更新base
final boolean casBase(long cmp, long val) {
return BASE.compareAndSet(this, cmp, val);
}
// cas更新base并返回旧值
final long getAndSetBase(long val) {
return (long)BASE.getAndSet(this, val);
}
// 对cellsBusy(自旋锁)成员变量进行更新,如果cellsBusy为0(自旋锁未被占用状态)则将cellsBusy置为1(占用状态)并返回true.如果cellsBusy为1(占用状态),获取自旋锁失败,不进行任何操作,返回false.
final boolean casCellsBusy() {
return CELLSBUSY.compareAndSet(this, 0, 1);
}
// 获取当前线程的probe(hash)值
static final int getProbe() {
return (int) THREAD_PROBE.get(Thread.currentThread());
}
// 通过移位和异或运算更新当前线程的probe值(hash值)并返回
static final int advanceProbe(int probe) {
probe ^= probe << 13;
probe ^= probe >>> 17;
probe ^= probe << 5;
THREAD_PROBE.set(Thread.currentThread(), probe);
return probe;
}
// X:累加值, fn:LongBinaryOperator接口,需要程序员实现接口中的applyAsLong方法, wasUncontended:是否存在竞争,如果CAS失败后wasUncontended为false
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
ThreadLocalRandom.current();
h = getProbe();
wasUncontended = true;
}
// 标记是否碰撞,用于控制扩容
boolean collide = false;
// 设置标签done
done:
for (;;) {
Cell[] cs; Cell c; int n; long v;
// cs=cells并判断cs是否非空且有元素
if ((cs = cells) != null && (n = cs.length) > 0) {
//计算索引:线程probe & (cells数组长度-1),获取cells数组索引处的元素判断是否为空
if ((c = cs[(n - 1) & h]) == null) {
// 判断自旋锁是否已经未被占用
if (cellsBusy == 0) {
// 新建cell对象r并初始化value=x
Cell r = new Cell(x);
// 双重检查是否锁未被占用,并获取锁(将cellsBusy修改为1)
if (cellsBusy == 0 && casCellsBusy()) {
try {
Cell[] rs; int m, j;
// 双重检查 判断cells数组是否非空且有元素且cells数组索引处的元素判断是否为空
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
// 在该索引处存储r,并跳到done处
rs[j] = r;
break done;
}
} finally {
// finally域中保证锁被释放
cellsBusy = 0;
}
continue;
}
}
collide = false;
}
else if (!wasUncontended)
wasUncontended = true;
else if (c.cas(v = c.value,(fn == null) ? v + x : fn.applyAsLong(v, x)))
break;
// 判断表大小是否达到上限或已经进行扩容
else if (n >= NCPU || cells != cs)
collide = false;
else if (!collide)
collide = true;
// 锁未占用则获取锁
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == cs)
// 2倍扩容
cells = Arrays.copyOf(cs, n << 1);
} finally {
// 保证锁被释放
cellsBusy = 0;
}
collide = false;
continue;
}
// 更新hash值,在下一次循环中继续计算探索新的索引 h & (length-1)
h = advanceProbe(h);
}
// 如果cells为null或者没有元素, 判断锁是否被占用, 如果没有占用,
// 判断cells等于cs(新建的cells[],还未初始化),并是否成功获取锁
else if (cellsBusy == 0 && cells == cs && casCellsBusy()) {
try {
if (cells == cs) {
// cells初始容量为2
Cell[] rs = new Cell[2];
// 新建cell对象初始化value=x,存储在线程probe & (2-1)索引处,将rs赋给cells并跳转到done处
rs[h & 1] = new Cell(x);
cells = rs;
break done;
}
} finally {
// 保证锁能够被释放
cellsBusy = 0;
}
}
// 如果cells为null或者无元素,自旋锁又被占用或者无法成功获取锁
// 使用fn的applyAsLong方法计算新的value值,调用casBase更新base,如果更新不成功,则自旋
else if (casBase(v = base, (fn == null) ? v + x : fn.applyAsLong(v, x)))
break done;
}
}
// 和longAccumulate原理类似
final void doubleAccumulate(double x, DoubleBinaryOperator fn,
boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
ThreadLocalRandom.current();
h = getProbe();
wasUncontended = true;
}
boolean collide = false;
done: for (;;) {
Cell[] cs; Cell c; int n; long v;
if ((cs = cells) != null && (n = cs.length) > 0) {
if ((c = cs[(n - 1) & h]) == null) {
if (cellsBusy == 0) {
Cell r = new Cell(Double.doubleToRawLongBits(x));
if (cellsBusy == 0 && casCellsBusy()) {
try {
Cell[] rs; int m, j;
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
break done;
}
} finally {
cellsBusy = 0;
}
continue;
}
}
collide = false;
}
else if (!wasUncontended)
wasUncontended = true;
else if (c.cas(v = c.value, apply(fn, v, x)))
break;
else if (n >= NCPU || cells != cs)
collide = false;
else if (!collide)
collide = true;
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == cs)
cells = Arrays.copyOf(cs, n << 1);
} finally {
cellsBusy = 0;
}
collide = false;
continue;
}
h = advanceProbe(h);
}
else if (cellsBusy == 0 && cells == cs && casCellsBusy()) {
try {
if (cells == cs) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(Double.doubleToRawLongBits(x));
cells = rs;
break done;
}
} finally {
cellsBusy = 0;
}
}
else if (casBase(v = base, apply(fn, v, x)))
break done;
}
}
// 因为cell对象中的value是long类型的, 先对v强转为double型, 然后调用DoubleBinaryOperator接口的applyAsDouble方法(需要程序员实现)计算新的值并返回
private static long apply(DoubleBinaryOperator fn, long v, double x) {
double d = Double.longBitsToDouble(v);
d = (fn == null) ? d + x : fn.applyAsDouble(d, x);
return Double.doubleToRawLongBits(d);
}
}
striped64是java8用来并发计数新加的组件,在单线程或者没有线程竞争的情况下,直接对base操作,当遇到多线程的时候,计算索引= 线程的probe(hash值) & (cells数组长度-1),找到对应的Cell, cas改变Cell中的值,最后的所求的结果就是base+所有Cell中的值,因此执行效率高。
64指的是计数64bit的数,即Long类型的数和Double类型的数。striped64是个抽象类,它的实现类有LongAdder,LongAccumulator, DoubleAdder, DoubleAccumulator(LongAccumulator和DoubleAccumulator各自在LongAdder和DoubleAdder的基础上,分别通过LongBinaryOperator和DoubleBinaryOperator接口,指定计算新的value的方法)
public class LongAdder extends Striped64 implements Serializable {
private static final long serialVersionUID = 7249069246863182397L;
public LongAdder(){}
public void add(long x) {
Cell[] cs; long b, v; int m; Cell c;
// 判断是cells是否非null,如果非null,接着CAS更新base并判断是否更新成功
if ((cs = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
// 双重检查cells是否为空,或者没有元素, 或者有元素但是线程对应索引处cell为null,或者更新value失败, 则调用longAccumulate方法探测位置并添加x
if (cs == null || (m = cs.length - 1) < 0 ||
(c = cs[getProbe() & m]) == null ||
!(uncontended = c.cas(v = c.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
public void increment() {
add(1L);
}
public void decrement() {
add(-1L);
}
// 获取所有cell的和
public long sum() {
Cell[] cs = cells;
long sum = base;
if (cs != null) {
for (Cell c : cs)
if (c != null)
sum += c.value;
}
return sum;
}
// cells全部清零
public void reset() {
Cell[] cs = cells;
base = 0L;
if (cs != null) {
for (Cell c : cs)
if (c != null)
c.reset();
}
}
// 计算cells的和并全部清零
public long sumThenReset() {
Cell[] cs = cells;
long sum = getAndSetBase(0L);
if (cs != null) {
for (Cell c : cs) {
if (c != null)
// 更新为0L并返回旧值
sum += c.getAndSet(0L);
}
}
return sum;
}
private static class SerializationProxy implements Serializable {
private static final long serialVersionUID = 7249069246863182397L;
private final long value;
SerializationProxy(LongAdder a) {
value = a.sum();
}
// 指定反序列化解析得到的对象
private Object readResolve() {
LongAdder a = new LongAdder();
a.base = value;
return a;
}
}
private Object writeReplace() {
return new SerializationProxy(this);
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.InvalidObjectException {
throw new java.io.InvalidObjectException("Proxy required");
}
}
对象被访问的时候是怎么被找到的?
创建一个对象的时候,在栈内存中会有一个引用变量,指向堆内存中该对象实例,访问该实例有两种方法:
句柄访问
JVM的堆内存中划分出一块内存来作为句柄池,引用变量中存储的就是对象的句柄地址,句柄中包含了对象实例数据与类型数据各自的具体地址信息。在内存垃圾收集之后,对象会移动,句柄访问不直接,访问速度较慢。
直接指针访问
Unsafe,VarHandle类就是获取对象在内存地址的偏移量得到对象的直接指针,直接指针的访问方式节省了一次指针定位的时间开销,速度较快。