java.utils.concurrent.atomic
此包下存放了大量java的原子类;所谓原子类,是java为了实现在多线程环境下保证对象实现原子性操作而设计的,可以避免在多线程环境中的同步问题(如使用 synchronized 关键字)。这意味着它们可以提供原子性的读-修改-写操作,不需要显式的同步机制。
原子类保证了原子性,并通过
volatile保证了一定程度上的有序性和可见性
基本类型的原子操作类 AtomicInteger
AtomicInteger提供了原子性的基本整型操作,如增加、减少、设置值等,无需显式加锁。
多线程下的非原子类
public class AtomicIntegerDemo {
private static Integer count = 0;
public static void increment() {
count++;
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
increment();
}
});
thread.start();
thread2.start();
thread.join();
thread2.join();
System.out.println("Count: " + count);
}
}
Count: 1384
public class AtomicIntegerDemo {
private static AtomicInteger count = new AtomicInteger(0);
public static void increment() {
count.incrementAndGet();
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
increment();
}
});
thread.start();
thread2.start();
thread.join();
thread2.join();
System.out.println("Count: " + count);
}
}
Count: 2000
可以看出原子类的作用
原子性实现
CAS (Compare and Swap)译为,比较并操作;是一类基本的处理多线程的算法。CAS 操作包含三个参数:当前值 V、期望值 A 和新值 B。当且仅当 V 的位置的值等于 A 时,才会将 A 替换为 B。否则,操作失败,并可能再次尝试。if (V == A) V = B else someting;
除了CAS,java还在虚拟机层面实现了Unsafe 类:sun.misc.Unsafe 类来进行底层的 CAS 操作。Unsafe 类提供了直接访问底层内存的方法。(唤醒了Rust记忆,计算机本身就是非安全的,哪怕是以安全著称的Rust也需要unsafe操作)
这个是最顶层的操作
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
上面的操作调用了Unsafe的getAndAddInt
// VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
@IntrinsicCandidate
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;
}
在UnSafe类中,程序不断去获得当前的值v,其中offset提供了内存偏移位置, 然后去执行CAS操作;失败则继续循环
上面分别涉及到两个方法
/** Volatile version of {@link #getInt(Object, long)} */
@IntrinsicCandidate
public native int getIntVolatile(Object o, long offset);
这个是getInt的保证可见性的版本
@IntrinsicCandidate
public final boolean weakCompareAndSetInt(Object o, long offset, int expected, int x) {
// o+offset译为当前值;expect设为期望值,x是新值
return compareAndSetInt(o, offset, expected, x);
}
/**
* Atomically updates Java variable to x if it is currently holding expected
*
* This operation has memory semantics of a {@code volatile} read and write.
* Corresponds to C11 atomic_compare_exchange_strong.
*
* @return true if successful
*/
@IntrinsicCandidate
// o+offset译为当前值;expect设为期望值,x是新值
public final native boolean compareAndSetInt(Object o, long offset, int expected, int x);
之前在循环中获得的 v 没有被改变,则o+offset == expect(x), CAS就会成功
CAS问题,无法处理A->B->A问题; 因为CAS只要求比较时符合要求就好,至于中间是否发生了变化,CAS不关心
其他操作方法基本和上面所叙述的运行相同,只不过是方法调用层次不同;当然除了传入数值,java还支持传入自定义函数,详情可见public final int updateAndGet(IntUnaryOperator updateFunction)
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()));
}
}
public final boolean weakCompareAndSetVolatile(int expectedValue, int newValue) {
return U.weakCompareAndSetInt(this, VALUE, expectedValue, newValue);
}
同于普通操作
- 先获取当前值作为期望值
- 然后获得目标值
- JVM中再取当前值与期望值比较
有序性与可见性
有序性与可见性由volatile与getIntVolatile提供
private volatile int value;
常见问题与易错点
- 误解原子性:认为任何基于
AtomicInteger的操作都是绝对线程安全的。实际上,原子性仅保证了单个操作的不可分割性,复杂逻辑依然可能需要额外同步。 - 过度依赖原子类:在某些情况下,过度使用原子类可能不如使用
synchronized或Lock来得直观和高效,特别是在涉及多个变量的复合操作时。
引用类型的原子操作类AtomicReference
Java只提供了基础数据类型的原则类,如果我们希望实现自己的类的原子性则要使用AtomicReference; 原子性,有序性与可见性的处理方式与上面相同, 只不过从底层操作从unsafe改为了varhandle
使用AtomicReference包装Integer
public class AtomicIntegerDemo {
private static AtomicReference<Integer> count = new AtomicReference<>(0);
public static void increment() {
count.updateAndGet(v -> v + 1);
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
increment();
}
});
thread.start();
thread2.start();
thread.join();
thread2.join();
System.out.println("Count: " + count);
}
}
常见问题与易错点
- 误解:认为任何基于
AtomicReference的操作都是绝对线程安全的。AtomicReference只保证对对象的值得改变是安全的,至于对象内部,这不是它的工作范围
例如
class myInt {
private int value;
public myInt(int value) {
this.value = value;
}
public void increment() {
value++;
}
public int getValue() {
return value;
}
}
public class AtomicReferenceDemo {
private static AtomicReference<myInt> count = new AtomicReference<>(new myInt(0));
public static void increment() {
count.updateAndGet(myInt -> {
myInt.increment();
return myInt;
});
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
increment();
}
});
thread.start();
thread2.start();
thread.join();
thread2.join();
System.out.println("Count: " + count.get().getValue());
}
}
Count: 1927 失败 上面出现问题是因为AtomicReference只关心myInt的值,但值得内部与AtomicReference无关
class myInt {
private volatile int value;
public myInt(int value) {
this.value = value;
}
public void increment() {
value++;
}
public int getValue() {
return value;
}
}
public class AtomicIntegerDemo {
private static AtomicReference<myInt> count = new AtomicReference<>(new myInt(0));
public static void increment() {
count.updateAndGet(myInt -> new myInt(myInt.getValue() + 1));
}
Count: 2000
数组的原子操作类AtomicIntegerArray
上面只提供了单个元素的原子类,如果需要使用数组,则可以使用AtomicIntegerArray;当然更常见的是使用CopyOnWriteArrayList;这个类的全部方法都是native方法,目前我还不会阅读:(
有序性与可见性
private final int[] array; // 没有在array上面实现,但对于单个元素应该实现了
为甚不用CopyOnWriteArrayList呢?
AtomicDouble
本人本想去寻找有关Double的原子类,Java内也有DoubleAdder这些类,不过目前并没有详细阅读DoubleAdder,只是发现内部依然使用的long存储具体值;目前由两种思路实现double的原子类
- 使用AtomicReference去包装
- 使用AtomicLong模拟
正好在stack overflow上找到了例子,仅供参考
stackoverflow.com/questions/5…
同时在google的utils包内也找到了double的原子类 guava.dev/releases/19…
结尾
更新字段的原子操作类
如果我们只需要某个类里的某个字段,那么就需要使用原子更新字段类,Atomic包提供了以下三个类:
- AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。通过静态工厂方法创建
newUpdater(Class<U> tclass, String fieldName);- AtomicLongFieldUpdater: 原子更新长整型字段的更新器。通过静态工厂方法创建
newUpdater(Class<U> tclass, String fieldName);- AtomicReferenceFieldUpdater 原子更新引用类型里的字段。比前两个方法的范围要广,可以更新任意类型的字段,通过静态的工厂方法创建
newUpdater(Class<U> tclass, Class<W> vclass, String fieldName);
感觉认识还不足,暂时只能写出这么多